Skip to content

Commit 0eebf97

Browse files
[otlp] OTLP Exporter Custom serializer Part 1 - WritingPrimitives (open-telemetry#5910)
Co-authored-by: Mikel Blanchard <[email protected]>
1 parent f1f2664 commit 0eebf97

File tree

4 files changed

+635
-0
lines changed

4 files changed

+635
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System.Buffers.Binary;
5+
using System.Diagnostics;
6+
using System.Runtime.CompilerServices;
7+
#if NETFRAMEWORK || NETSTANDARD2_0
8+
using System.Runtime.InteropServices;
9+
#endif
10+
using System.Text;
11+
12+
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
13+
14+
internal static class ProtobufSerializer
15+
{
16+
private const uint UInt128 = 0x80;
17+
private const ulong ULong128 = 0x80;
18+
private const int Fixed32Size = 4;
19+
private const int Fixed64Size = 8;
20+
21+
private static readonly Encoding Utf8Encoding = Encoding.UTF8;
22+
23+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
24+
internal static uint GetTagValue(int fieldNumber, ProtobufWireType wireType) => ((uint)(fieldNumber << 3)) | (uint)wireType;
25+
26+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
27+
internal static int WriteTag(byte[] buffer, int writePosition, int fieldNumber, ProtobufWireType type) => WriteVarInt32(buffer, writePosition, GetTagValue(fieldNumber, type));
28+
29+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
30+
internal static int WriteLength(byte[] buffer, int writePosition, int length) => WriteVarInt32(buffer, writePosition, (uint)length);
31+
32+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
33+
internal static int WriteTagAndLength(byte[] buffer, int writePosition, int contentLength, int fieldNumber, ProtobufWireType type)
34+
{
35+
writePosition = WriteTag(buffer, writePosition, fieldNumber, type);
36+
writePosition = WriteLength(buffer, writePosition, contentLength);
37+
38+
return writePosition;
39+
}
40+
41+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42+
internal static void WriteReservedLength(byte[] buffer, int writePosition, int length)
43+
{
44+
int byteLength = 0;
45+
int? firstByte = null;
46+
int? secondByte = null;
47+
int? thirdByte = null;
48+
int? fourthByte = null;
49+
50+
do
51+
{
52+
switch (byteLength)
53+
{
54+
case 0:
55+
firstByte = length & 0x7F;
56+
break;
57+
case 1:
58+
secondByte = length & 0x7F;
59+
break;
60+
case 2:
61+
thirdByte = length & 0x7F;
62+
break;
63+
case 3:
64+
fourthByte = length & 0x7F;
65+
break;
66+
}
67+
68+
length >>= 7;
69+
byteLength++;
70+
}
71+
while (length > 0);
72+
73+
if (fourthByte.HasValue)
74+
{
75+
buffer[writePosition++] = (byte)(firstByte!.Value | 0x80);
76+
buffer[writePosition++] = (byte)(secondByte!.Value | 0x80);
77+
buffer[writePosition++] = (byte)(thirdByte!.Value | 0x80);
78+
buffer[writePosition++] = (byte)fourthByte!.Value;
79+
}
80+
else if (thirdByte.HasValue)
81+
{
82+
buffer[writePosition++] = (byte)(firstByte!.Value | 0x80);
83+
buffer[writePosition++] = (byte)(secondByte!.Value | 0x80);
84+
buffer[writePosition++] = (byte)(thirdByte!.Value | 0x80);
85+
buffer[writePosition++] = 0;
86+
}
87+
else if (secondByte.HasValue)
88+
{
89+
buffer[writePosition++] = (byte)(firstByte!.Value | 0x80);
90+
buffer[writePosition++] = (byte)(secondByte!.Value | 0x80);
91+
buffer[writePosition++] = 0x80;
92+
buffer[writePosition++] = 0;
93+
}
94+
else
95+
{
96+
buffer[writePosition++] = (byte)(firstByte!.Value | 0x80);
97+
buffer[writePosition++] = 0x80;
98+
buffer[writePosition++] = 0x80;
99+
buffer[writePosition++] = 0;
100+
}
101+
}
102+
103+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
104+
internal static int WriteBoolWithTag(byte[] buffer, int writePosition, int fieldNumber, bool value)
105+
{
106+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.VARINT);
107+
buffer[writePosition++] = value ? (byte)1 : (byte)0;
108+
return writePosition;
109+
}
110+
111+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
112+
internal static int WriteEnumWithTag(byte[] buffer, int writePosition, int fieldNumber, int value)
113+
{
114+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.VARINT);
115+
buffer[writePosition++] = (byte)value;
116+
return writePosition;
117+
}
118+
119+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
120+
internal static int WriteFixed32LittleEndianFormat(byte[] buffer, int writePosition, uint value)
121+
{
122+
Span<byte> span = new(buffer, writePosition, Fixed32Size);
123+
BinaryPrimitives.WriteUInt32LittleEndian(span, value);
124+
writePosition += Fixed32Size;
125+
126+
return writePosition;
127+
}
128+
129+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
130+
internal static int WriteFixed64LittleEndianFormat(byte[] buffer, int writePosition, ulong value)
131+
{
132+
Span<byte> span = new(buffer, writePosition, Fixed64Size);
133+
BinaryPrimitives.WriteUInt64LittleEndian(span, value);
134+
writePosition += Fixed64Size;
135+
136+
return writePosition;
137+
}
138+
139+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
140+
internal static int WriteFixed32WithTag(byte[] buffer, int writePosition, int fieldNumber, uint value)
141+
{
142+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.I32);
143+
writePosition = WriteFixed32LittleEndianFormat(buffer, writePosition, value);
144+
145+
return writePosition;
146+
}
147+
148+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
149+
internal static int WriteFixed64WithTag(byte[] buffer, int writePosition, int fieldNumber, ulong value)
150+
{
151+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.I64);
152+
writePosition = WriteFixed64LittleEndianFormat(buffer, writePosition, value);
153+
154+
return writePosition;
155+
}
156+
157+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
158+
internal static int WriteVarInt32(byte[] buffer, int writePosition, uint value)
159+
{
160+
while (value >= UInt128)
161+
{
162+
buffer[writePosition++] = (byte)(0x80 | (value & 0x7F));
163+
value >>= 7;
164+
}
165+
166+
buffer[writePosition++] = (byte)value;
167+
return writePosition;
168+
}
169+
170+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
171+
internal static int WriteVarInt64(byte[] buffer, int writePosition, ulong value)
172+
{
173+
while (value >= ULong128)
174+
{
175+
buffer[writePosition++] = (byte)(0x80 | (value & 0x7F));
176+
value >>= 7;
177+
}
178+
179+
buffer[writePosition++] = (byte)value;
180+
return writePosition;
181+
}
182+
183+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
184+
internal static int WriteInt64WithTag(byte[] buffer, int writePosition, int fieldNumber, ulong value)
185+
{
186+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.VARINT);
187+
writePosition = WriteVarInt64(buffer, writePosition, value);
188+
189+
return writePosition;
190+
}
191+
192+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
193+
internal static int WriteDoubleWithTag(byte[] buffer, int writePosition, int fieldNumber, double value)
194+
{
195+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.I64);
196+
writePosition = WriteFixed64LittleEndianFormat(buffer, writePosition, (ulong)BitConverter.DoubleToInt64Bits(value));
197+
198+
return writePosition;
199+
}
200+
201+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
202+
internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fieldNumber, string value)
203+
{
204+
Debug.Assert(value != null, "value was null");
205+
206+
return WriteStringWithTag(buffer, writePosition, fieldNumber, value.AsSpan());
207+
}
208+
209+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
210+
internal static int WriteStringWithTag(byte[] buffer, int writePosition, int fieldNumber, ReadOnlySpan<char> value)
211+
{
212+
#if NETFRAMEWORK || NETSTANDARD2_0
213+
int numberOfUtf8CharsInString;
214+
unsafe
215+
{
216+
fixed (char* strPtr = &GetNonNullPinnableReference(value))
217+
{
218+
numberOfUtf8CharsInString = Utf8Encoding.GetByteCount(strPtr, value.Length);
219+
}
220+
}
221+
#else
222+
int numberOfUtf8CharsInString = Utf8Encoding.GetByteCount(value);
223+
#endif
224+
225+
writePosition = WriteTag(buffer, writePosition, fieldNumber, ProtobufWireType.LEN);
226+
writePosition = WriteLength(buffer, writePosition, numberOfUtf8CharsInString);
227+
228+
#if NETFRAMEWORK || NETSTANDARD2_0
229+
unsafe
230+
{
231+
fixed (char* strPtr = &GetNonNullPinnableReference(value))
232+
{
233+
fixed (byte* bufferPtr = buffer)
234+
{
235+
var bytesWritten = Utf8Encoding.GetBytes(strPtr, value.Length, bufferPtr + writePosition, numberOfUtf8CharsInString);
236+
Debug.Assert(bytesWritten == numberOfUtf8CharsInString, "bytesWritten did not match numberOfUtf8CharsInString");
237+
}
238+
}
239+
}
240+
#else
241+
var bytesWritten = Utf8Encoding.GetBytes(value, buffer.AsSpan().Slice(writePosition));
242+
Debug.Assert(bytesWritten == numberOfUtf8CharsInString, "bytesWritten did not match numberOfUtf8CharsInString");
243+
#endif
244+
245+
writePosition += numberOfUtf8CharsInString;
246+
return writePosition;
247+
}
248+
249+
#if NETFRAMEWORK || NETSTANDARD2_0
250+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
251+
private static unsafe ref T GetNonNullPinnableReference<T>(ReadOnlySpan<T> span)
252+
=> ref (span.Length != 0) ? ref Unsafe.AsRef(in MemoryMarshal.GetReference(span)) : ref Unsafe.AsRef<T>((void*)1);
253+
#endif
254+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
5+
6+
/// <summary>
7+
/// Wire types within protobuf encoding.
8+
/// https://protobuf.dev/programming-guides/encoding/#structure.
9+
/// </summary>
10+
internal enum ProtobufWireType : uint
11+
{
12+
/// <summary>
13+
/// Variable-length integer.
14+
/// Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum.
15+
/// </summary>
16+
VARINT = 0,
17+
18+
/// <summary>
19+
/// A fixed-length 64-bit value.
20+
/// Used for fixed64, sfixed64, double.
21+
/// </summary>
22+
I64 = 1,
23+
24+
/// <summary>
25+
/// A length-delimited value.
26+
/// Used for string, bytes, embedded messages, packed repeated fields.
27+
/// </summary>
28+
LEN = 2,
29+
30+
/// <summary>
31+
/// Group Start value.
32+
/// (Deprecated).
33+
/// </summary>
34+
SGROUP = 3,
35+
36+
/// <summary>
37+
/// Group End value.
38+
/// (Deprecated).
39+
/// </summary>
40+
EGROUP = 4,
41+
42+
/// <summary>
43+
/// A fixed-length 32-bit value.
44+
/// Used for fixed32, sfixed32, float.
45+
/// </summary>
46+
I32 = 5,
47+
}

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
https://github.com/open-telemetry/opentelemetry-dotnet/pull/5520#discussion_r1556221048
1717
and https://github.com/dotnet/runtime/issues/92509 -->
1818
<NoWarn>$(NoWarn);SYSLIB1100;SYSLIB1101</NoWarn>
19+
<AllowUnsafeBlocks Condition="'$(TargetFramework)' == 'netstandard2.0' OR '$(TargetFramework)' == '$(NetFrameworkMinimumSupportedVersion)'">true</AllowUnsafeBlocks>
1920
</PropertyGroup>
2021

2122
<ItemGroup>

0 commit comments

Comments
 (0)