Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Exporter.Geneva] Update GenevaMetricExporter to export Exemplars #1069

2 changes: 1 addition & 1 deletion build/Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<MicrosoftPublicApiAnalyzersPkgVer>[3.3.3]</MicrosoftPublicApiAnalyzersPkgVer>
<MicrosoftSourceLinkGitHubPkgVer>[1.1.1,2.0)</MicrosoftSourceLinkGitHubPkgVer>
<OpenTelemetryCoreLatestVersion>[1.4.0,2.0)</OpenTelemetryCoreLatestVersion>
<OpenTelemetryCoreLatestPrereleaseVersion>[1.4.0,2.0)</OpenTelemetryCoreLatestPrereleaseVersion>
<OpenTelemetryCoreLatestPrereleaseVersion>[1.5.0-alpha.1]</OpenTelemetryCoreLatestPrereleaseVersion>
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
<StackExchangeRedisPkgVer>[2.1.58,3.0)</StackExchangeRedisPkgVer>
<StyleCopAnalyzersPkgVer>[1.2.0-beta.435,2.0)</StyleCopAnalyzersPkgVer>
<SystemNetHttp>[4.3.4,)</SystemNetHttp>
Expand Down
9 changes: 7 additions & 2 deletions src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
* Changed the behavior of Unix domain socket connection at startup. Before this
change, the exporter initialization would throw exception if the target Unix
Domain Socket does not exist. After this change, the exporter initialization
would return success and the exporting background thread will try to
establish the connection.
would return success and the exporting background thread will try to establish
the connection.
([#935](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/935))

* Update OTel SDK version to `1.5.0-alpha.1`.
* Update GenevaMetricExporter to use TLV format serialization.
* Add support for exporting exemplars.
([#1069](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1069))

## 1.4.0

Released 2023-Feb-27
Expand Down
530 changes: 504 additions & 26 deletions src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs

Large diffs are not rendered by default.

208 changes: 208 additions & 0 deletions src/OpenTelemetry.Exporter.Geneva/Metrics/MetricSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,221 @@ public static void SerializeUInt64(byte[] buffer, ref int bufferIndex, ulong val
buffer[bufferIndex + 7] = (byte)(value >> 0x38);
bufferIndex += sizeof(ulong);
}

/// <summary>
/// Writes the long to buffer.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="bufferIndex">Index of the buffer.</param>
/// <param name="value">The value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeInt64(byte[] buffer, ref int bufferIndex, long value)
{
if (bufferIndex + sizeof(long) >= buffer.Length)
{
}

buffer[bufferIndex] = (byte)value;
buffer[bufferIndex + 1] = (byte)(value >> 8);
buffer[bufferIndex + 2] = (byte)(value >> 0x10);
buffer[bufferIndex + 3] = (byte)(value >> 0x18);
buffer[bufferIndex + 4] = (byte)(value >> 0x20);
buffer[bufferIndex + 5] = (byte)(value >> 0x28);
buffer[bufferIndex + 6] = (byte)(value >> 0x30);
buffer[bufferIndex + 7] = (byte)(value >> 0x38);
bufferIndex += sizeof(long);
}

/// <summary>
/// Writes the double to buffer.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="bufferIndex">Index of the buffer.</param>
/// <param name="value">The value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void SerializeFloat64(byte[] buffer, ref int bufferIndex, double value)
{
if (bufferIndex + sizeof(double) >= buffer.Length)
{
// TODO: What should we do when the data is invalid?
}

fixed (byte* bp = buffer)
{
*(double*)(bp + bufferIndex) = value;
}

bufferIndex += sizeof(double);
}

/// <summary>
/// Writes the base128 string to buffer.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="bufferIndex">Index of the buffer.</param>
/// <param name="value">The value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeBase128String(byte[] buffer, ref int bufferIndex, string value)
{
if (!string.IsNullOrEmpty(value))
{
if (bufferIndex + value.Length + sizeof(short) >= buffer.Length)
{
}

var encodedValue = Encoding.UTF8.GetBytes(value);
SerializeUInt64AsBase128(buffer, ref bufferIndex, (ulong)encodedValue.Length);
Array.Copy(encodedValue, 0, buffer, bufferIndex, encodedValue.Length);
bufferIndex += encodedValue.Length;
}
else
{
SerializeInt16(buffer, ref bufferIndex, 0);
}
}

/// <summary>
/// Writes unsigned int value Base-128 encoded.
/// </summary>
/// <param name="buffer">Buffer used for writing.</param>
/// <param name="offset">Offset to start with. Will be moved to the next byte after written.</param>
/// <param name="value">Value to write.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeUInt32AsBase128(byte[] buffer, ref int offset, uint value)
{
SerializeUInt64AsBase128(buffer, ref offset, value);
}

/// <summary>
/// Writes ulong value Base-128 encoded to the buffer starting from the specified offset.
/// </summary>
/// <param name="buffer">Buffer used for writing.</param>
/// <param name="offset">Offset to start with. Will be moved to the next byte after written.</param>
/// <param name="value">Value to write.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeUInt64AsBase128(byte[] buffer, ref int offset, ulong value)
{
var t = value;
do
{
var b = (byte)(t & 0x7f);
t >>= 7;
if (t > 0)
{
b |= 0x80;
}

buffer[offset++] = b;
}
while (t > 0);
}

/// <summary>
/// Writes int value Base-128 encoded.
/// </summary>
/// <param name="buffer">Buffer used for writing.</param>
/// <param name="offset">Offset to start with. Will be moved to the next byte after written.</param>
/// <param name="value">Value to write.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeInt32AsBase128(byte[] buffer, ref int offset, int value)
{
SerializeInt64AsBase128(buffer, ref offset, value);
}

/// <summary>
/// Writes long value Base-128 encoded.
/// </summary>
/// <param name="buffer">Buffer used for writing.</param>
/// <param name="offset">Offset to start with. Will be moved to the next byte after written.</param>
/// <param name="value">Value to write.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeInt64AsBase128(byte[] buffer, ref int offset, long value)
{
var negative = value < 0;
var t = negative ? -value : value;
var first = true;
do
{
byte b;
if (first)
{
b = (byte)(t & 0x3f);
t >>= 6;
if (negative)
{
b = (byte)(b | 0x40);
}

first = false;
}
else
{
b = (byte)(t & 0x7f);
t >>= 7;
}

if (t > 0)
{
b |= 0x80;
}

buffer[offset++] = b;
}
while (t > 0);
}

/// <summary>
/// Writes the encoded string to buffer.
/// </summary>
/// <param name="buffer">The buffer to write data into.</param>
/// <param name="bufferIndex">Index of the buffer.</param>
/// <param name="data">Source data.</param>
/// <param name="dataLength"> Number of bytes to copy.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeSpanOfBytes(byte[] buffer, ref int bufferIndex, Span<byte> data, int dataLength)
{
if (bufferIndex + dataLength + sizeof(short) >= buffer.Length)
{
}

ReadOnlySpan<byte> source = data.Slice(0, dataLength);
var target = new Span<byte>(buffer, bufferIndex, dataLength);

source.CopyTo(target);
bufferIndex += dataLength;
}
}

internal enum MetricEventType
{
ULongMetric = 50,
DoubleMetric = 55,
ExternallyAggregatedULongDistributionMetric = 56,
TLV = 70,
}

internal enum PayloadType
{
AccountName = 1,
Namespace = 2,
MetricName = 3,
Dimensions = 4,
ULongMetric = 5,
DoubleMetric = 6,
ExternallyAggregatedULongDistributionMetric = 8,
HistogramULongValueCountPairs = 12,
Exemplars = 15,
}

[Flags]
internal enum ExemplarFlags : byte
{
None = 0x0,
IsMetricValueDoubleStoredAsLong = 0x1,
IsTimestampAvailable = 0x2,
SpanIdExists = 0x4,
TraceIdExists = 0x8,
SampleCountExists = 0x10,
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,9 @@ private void DoubleMetricEvent()
private void ExternallyAggregatedDoubleDistributionMetric()
{
}

[Event((int)MetricEventType.TLV)]
private void TLVMetricEvent()
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="OpenTelemetry" Version="$(OpenTelemetryCoreLatestVersion)" />
<PackageReference Include="OpenTelemetry" Version="$(OpenTelemetryCoreLatestPrereleaseVersion)" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,12 +569,15 @@ public void SuccessfulExportOnLinux()
var metricPoint = metricPointsEnumerator.Current;
var metricDataValue = Convert.ToUInt64(metricPoint.GetSumLong());
var metricData = new MetricData { UInt64Value = metricDataValue };
var bodyLength = exporter.SerializeMetric(

var exemplars = metricPoint.GetExemplars();
var bodyLength = exporter.SerializeMetricWithTLV(
MetricEventType.ULongMetric,
metric.Name,
metricPoint.EndTime.ToFileTime(),
metricPoint.Tags,
metricData);
metricData,
exemplars);

// Wait a little more than the ExportInterval for the exporter to export the data.
Task.Delay(5500).Wait();
Expand All @@ -589,6 +592,9 @@ public void SuccessfulExportOnLinux()
// BinaryHeader (fixed payload) + variable payload which starts with MetricPayload
Assert.Equal(bodyLength + fixedPayloadLength, receivedDataSize);

// TODO: Update the unit test to test TLV based serialization

/*
var stream = new KaitaiStream(receivedData);
var data = new MetricsContract(stream);

Expand All @@ -611,6 +617,7 @@ public void SuccessfulExportOnLinux()

Assert.Equal((ushort)MetricEventType.ULongMetric, data.EventId);
Assert.Equal(bodyLength, data.LenBody);
*/
}
finally
{
Expand Down