From 0351a0bcbcf8c43bcaab1fb17a01a9b845ca67c2 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Wed, 30 Oct 2024 10:17:23 -0700 Subject: [PATCH] [geneva] Add support for sending metrics in OTLP format over UDS (#2261) Co-authored-by: Cijo Thomas --- .../CHANGELOG.md | 21 ++- .../Metrics/GenevaMetricExporter.cs | 42 ++++- .../OtlpProtobufMetricExporter.cs | 31 ++-- .../OtlpProtobuf/OtlpProtobufSerializer.cs | 27 ++-- .../MetricUnixDomainSocketDataTransport.cs | 10 +- src/OpenTelemetry.Exporter.Geneva/README.md | 73 +++++++-- .../Exporter/MetricExporterBenchmarks.cs | 4 +- .../OtlpProtobufMetricExporterTests.cs | 146 ++++++++++++------ ...lpProtobufMetricExporterWithPrefixTests.cs | 12 ++ ...rotobufMetricExporterWithoutPrefixTests.cs | 12 ++ 10 files changed, 274 insertions(+), 104 deletions(-) create mode 100644 test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithPrefixTests.cs create mode 100644 test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithoutPrefixTests.cs diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index e1fbedc7df..7ca97b842c 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -5,14 +5,19 @@ * Drop support for .NET 6 as this target is no longer supported. ([#2117](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2117)) -* Added support for exporting metrics via - [user_events](https://docs.kernel.org/trace/user_events.html) on Linux when - OTLP protobuf encoding is enabled via the - `PrivatePreviewEnableOtlpProtobufEncoding=true` connection string switch. With - this, `PrivatePreviewEnableOtlpProtobufEncoding=true` is now supported on both - Widows and Linux. Windows uses ETW as transport, while Linux uses user_events - as transport. - ([#2113](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2113)) +* Added support for exporting metrics on Linux when OTLP protobuf encoding is + enabled via the `PrivatePreviewEnableOtlpProtobufEncoding=true` connection + string switch. `PrivatePreviewEnableOtlpProtobufEncoding=true` is now + supported on both Windows and Linux. + + * `user_events` transport: + [#2113](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2113). + + * Unix domain socket transport: + [#2261](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2261). + + For configuration details see: + [OtlpProtobufEncoding](./README.md#otlpprotobufencoding). ## 1.9.0 diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs index ed78dda6ee..8607744849 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporter.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Reflection; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using OpenTelemetry.Exporter.Geneva.Metrics; using OpenTelemetry.Internal; @@ -52,14 +53,49 @@ public GenevaMetricExporter(GenevaMetricExporterOptions options) if (connectionStringBuilder.PrivatePreviewEnableOtlpProtobufEncoding) { + IMetricDataTransport transport; + + if (connectionStringBuilder.Protocol == TransportProtocol.Unix) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw new ArgumentException("Unix domain socket should not be used on Windows."); + } + + var unixDomainSocketPath = connectionStringBuilder.ParseUnixDomainSocketPath(); + + transport = new MetricUnixDomainSocketDataTransport(unixDomainSocketPath); + } + else + { +#if NET6_0_OR_GREATER + transport = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? MetricUnixUserEventsDataTransport.Instance + : MetricWindowsEventTracingDataTransport.Instance; +#else + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw new NotSupportedException("Exporting data in protobuf format is not supported on Linux."); + } + + transport = MetricWindowsEventTracingDataTransport.Instance; +#endif + } + + connectionStringBuilder.TryGetMetricsAccountAndNamespace( + out var metricsAccount, + out var metricsNamespace); + var otlpProtobufExporter = new OtlpProtobufMetricExporter( () => { return this.Resource; }, - connectionStringBuilder, + transport, + metricsAccount, + metricsNamespace, options.PrepopulatedMetricDimensions); - this.exporter = otlpProtobufExporter; - this.exportMetrics = otlpProtobufExporter.Export; + + this.exporter = otlpProtobufExporter; } else { diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufMetricExporter.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufMetricExporter.cs index 835c6cac1d..1931cf2dd4 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufMetricExporter.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -using System.Runtime.InteropServices; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; @@ -18,30 +17,22 @@ internal sealed class OtlpProtobufMetricExporter : IDisposable public OtlpProtobufMetricExporter( Func getResource, - ConnectionStringBuilder? connectionStringBuilder, + IMetricDataTransport transport, + string? metricsAccount, + string? metricsNamespace, IReadOnlyDictionary? prepopulatedMetricDimensions) { Debug.Assert(getResource != null, "getResource was null"); + Debug.Assert(transport != null, "transport was null"); this.getResource = getResource!; -#if NET6_0_OR_GREATER - IMetricDataTransport transport = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? MetricUnixUserEventsDataTransport.Instance - : MetricWindowsEventTracingDataTransport.Instance; -#else - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - throw new NotSupportedException("Exporting data in protobuf format is not supported on Linux."); - } - - var transport = MetricWindowsEventTracingDataTransport.Instance; -#endif - this.otlpProtobufSerializer = new OtlpProtobufSerializer( - transport, - connectionStringBuilder, - prepopulatedMetricDimensions); + transport!, + metricsAccount, + metricsNamespace, + prepopulatedMetricDimensions, + prefixBufferWithUInt32LittleEndianLength: transport is MetricUnixDomainSocketDataTransport); } public ExportResult Export(in Batch batch) @@ -59,5 +50,9 @@ public ExportResult Export(in Batch batch) public void Dispose() { + if (this.otlpProtobufSerializer.MetricDataTransport is MetricUnixDomainSocketDataTransport udsTransport) + { + udsTransport.Dispose(); + } } } diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufSerializer.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufSerializer.cs index 73123d6b5f..e837fc327c 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufSerializer.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/OtlpProtobuf/OtlpProtobufSerializer.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Buffers.Binary; using System.Diagnostics; using System.Globalization; using OpenTelemetry.Metrics; @@ -17,6 +18,7 @@ internal sealed class OtlpProtobufSerializer private readonly Dictionary> scopeMetrics = new(); private readonly string? metricNamespace; private readonly string? metricAccount; + private readonly bool prefixBufferWithUInt32LittleEndianLength; private readonly byte[]? prepopulatedNumberDataPointAttributes; private readonly int prepopulatedNumberDataPointAttributesLength; private readonly byte[]? prepopulatedHistogramDataPointAttributes; @@ -37,12 +39,17 @@ internal sealed class OtlpProtobufSerializer public OtlpProtobufSerializer( IMetricDataTransport metricDataTransport, - ConnectionStringBuilder? connectionStringBuilder, - IReadOnlyDictionary? prepopulatedMetricDimensions) + string? metricsAccount, + string? metricsNamespace, + IReadOnlyDictionary? prepopulatedMetricDimensions, + bool prefixBufferWithUInt32LittleEndianLength = false) { Debug.Assert(metricDataTransport != null, "metricDataTransport was null"); this.MetricDataTransport = metricDataTransport!; + this.metricAccount = metricsAccount; + this.metricNamespace = metricsNamespace; + this.prefixBufferWithUInt32LittleEndianLength = prefixBufferWithUInt32LittleEndianLength; // Taking a arbitrary number here for writing attributes. byte[] temp = new byte[20000]; @@ -68,14 +75,6 @@ public OtlpProtobufSerializer( Array.Copy(temp, this.prepopulatedExponentialHistogramDataPointAttributes, cursor); this.prepopulatedExponentialHistogramDataPointAttributesLength = cursor; } - - if (connectionStringBuilder?.TryGetMetricsAccountAndNamespace( - out var metricsAccount, - out var metricsNamespace) == true) - { - this.metricAccount = metricsAccount; - this.metricNamespace = metricsNamespace; - } } internal static void WriteInstrumentDetails(byte[] buffer, ref int cursor, Metric metric) @@ -222,7 +221,7 @@ internal void ClearScopeMetrics() internal void SerializeResourceMetrics(byte[] buffer, Resource resource) { - int cursor = 0; + int cursor = this.prefixBufferWithUInt32LittleEndianLength ? 4 : 0; this.resourceMetricTagAndLengthIndex = cursor; @@ -734,11 +733,15 @@ private void WriteIndividualMessageTagsAndLength(byte[] buffer, ref int cursor, // Write resource metric tag and length ProtobufSerializerHelper.WriteTagAndLengthPrefix(buffer, ref resourceMetricIndex, cursor - this.resourceMetricValueIndex, FieldNumberConstants.ResourceMetrics_resource, WireType.LEN); + + if (this.prefixBufferWithUInt32LittleEndianLength) + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)cursor - 4); + } } private void SendMetricPoint(byte[] buffer, ref int cursor) { - // TODO: Extend this for user_events. this.MetricDataTransport.SendOtlpProtobufEvent(buffer, cursor); } diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDomainSocketDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDomainSocketDataTransport.cs index 4faa04560f..70856642c1 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDomainSocketDataTransport.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/Transport/MetricUnixDomainSocketDataTransport.cs @@ -28,6 +28,11 @@ public void Send(MetricEventType eventType, byte[] body, int size) this.udsDataTransport.Send(body, size + this.fixedPayloadLength); } + public void SendOtlpProtobufEvent(byte[] body, int size) + { + this.udsDataTransport.Send(body, size); + } + public void Dispose() { if (this.isDisposed) @@ -38,9 +43,4 @@ public void Dispose() this.udsDataTransport.Dispose(); this.isDisposed = true; } - - public void SendOtlpProtobufEvent(byte[] body, int size) - { - throw new NotImplementedException(); - } } diff --git a/src/OpenTelemetry.Exporter.Geneva/README.md b/src/OpenTelemetry.Exporter.Geneva/README.md index 2ba0725906..2410808596 100644 --- a/src/OpenTelemetry.Exporter.Geneva/README.md +++ b/src/OpenTelemetry.Exporter.Geneva/README.md @@ -270,23 +270,72 @@ For example: ##### OtlpProtobufEncoding -On Windows set `PrivatePreviewEnableOtlpProtobufEncoding=true` on the -`ConnectionString` to opt-in to the experimental feature for changing the -underlying serialization format to binary protobuf following the schema defined -in [OTLP +An experimental feature flag is available to opt-into changing the underlying +serialization format to binary protobuf following the schema defined in [OTLP specification](https://github.com/open-telemetry/opentelemetry-proto/blob/v1.1.0/opentelemetry/proto/metrics/v1/metrics.proto). +When using OTLP protobuf encoding `Account` and `Namespace` are **NOT** required +to be set on the `ConnectionString`. The recommended approach is to use +OpenTelemetry Resource instead: + +```csharp +using var meterProvider = Sdk.CreateMeterProviderBuilder() + // Other configuration not shown + .ConfigureResource(r => r.AddAttributes( + new Dictionary() + { + ["_microsoft_metrics_account"] = "MetricsAccountGoesHere", + ["_microsoft_metrics_namespace"] = "MetricsNamespaceGoesHere", + })) + .AddGenevaMetricExporter(options => + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + options.ConnectionString = "PrivatePreviewEnableOtlpProtobufEncoding=true"; + } + else + { + // Note: 1.10.0+ version required to use OTLP protobuf encoding on Linux + + // Use Unix domain socket mode + options.ConnectionString = "Endpoint=unix:{OTLP UDS Path};PrivatePreviewEnableOtlpProtobufEncoding=true"; + + // Use user_events mode (preferred but considered experimental as this is a new capability in Linux kernel) + // options.ConnectionString = "PrivatePreviewEnableOtlpProtobufEncoding=true"; + } + }) + .Build(); +``` + +###### Windows + +To send metric data over ETW using OTLP protobuf encoding set +`PrivatePreviewEnableOtlpProtobufEncoding=true` on the `ConnectionString`. + +###### Linux + As of `1.10.0` `PrivatePreviewEnableOtlpProtobufEncoding=true` is also supported -on Linux. On Linux when using `PrivatePreviewEnableOtlpProtobufEncoding=true` an -`Endpoint` is **NOT** required to be provided on `ConnectionString`. For -example: `Endpoint=unix:Account={MetricAccount};Namespace={MetricNamespace}`. +on Linux. + +###### When using unix domain socket + +To send metric data over UDS using OTLP protobuf encoding set the `Endpoint` to +use the correct `OtlpSocketPath` path and set +`PrivatePreviewEnableOtlpProtobufEncoding=true` on the `ConnectionString`: +`Endpoint=unix:{OTLP UDS Path};PrivatePreviewEnableOtlpProtobufEncoding=true`. > [!IMPORTANT] -> When `PrivatePreviewEnableOtlpProtobufEncoding` is enabled on Linux metrics -> are written using -> [user_events](https://docs.kernel.org/trace/user_events.html). `user_events` -> are a newer feature of the Linux kernel and require a distro with the feature -> enabled. +> OTLP over UDS requires a different socket path than TLV over UDS. + +###### When using user_events + +> [!IMPORTANT] +> [user_events](https://docs.kernel.org/trace/user_events.html) are a newer +> feature of the Linux kernel and require a distro with the feature enabled. + +To send metric data over user_events using OTLP protobuf encoding do **NOT** +specify an `Endpoint` and set `PrivatePreviewEnableOtlpProtobufEncoding=true` on +the `ConnectionString`. #### `MetricExportIntervalMilliseconds` (optional) diff --git a/test/OpenTelemetry.Exporter.Geneva.Benchmarks/Exporter/MetricExporterBenchmarks.cs b/test/OpenTelemetry.Exporter.Geneva.Benchmarks/Exporter/MetricExporterBenchmarks.cs index 0e45b72a37..70c343a0fc 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Benchmarks/Exporter/MetricExporterBenchmarks.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Benchmarks/Exporter/MetricExporterBenchmarks.cs @@ -111,12 +111,12 @@ public void Setup() this.tlvMetricsExporter = new TlvMetricExporter(connectionStringBuilder, exporterOptions.PrepopulatedMetricDimensions); // Using test transport here with noop to benchmark just the serialization part. - this.otlpProtobufSerializer = new OtlpProtobufSerializer(new TestTransport(), null, null); + this.otlpProtobufSerializer = new OtlpProtobufSerializer(new TestTransport(), metricsAccount: null, metricsNamespace: null, prepopulatedMetricDimensions: null); var resourceBuilder = ResourceBuilder.CreateDefault().Clear() .AddAttributes(new[] { new KeyValuePair("TestResourceKey", "TestResourceValue") }); this.resource = resourceBuilder.Build(); - this.otlpProtobufMetricExporter = new OtlpProtobufMetricExporter(() => { return this.resource; }, null, null); + this.otlpProtobufMetricExporter = new OtlpProtobufMetricExporter(() => { return this.resource; }, new TestTransport(), metricsAccount: null, metricsNamespace: null, prepopulatedMetricDimensions: null); this.buffer = new byte[GenevaMetricExporter.BufferSize]; this.counterMetricPointWith3Dimensions = this.GenerateCounterMetricItemWith3Dimensions(out this.counterMetricDataWith3Dimensions); diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterTests.cs index cc8d84b9cc..4870b6aee1 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterTests.cs @@ -14,7 +14,7 @@ namespace OpenTelemetry.Exporter.Geneva.Tests; -public class OtlpProtobufMetricExporterTests +public abstract class OtlpProtobufMetricExporterTests { public TagList TagList; @@ -50,7 +50,7 @@ public class OtlpProtobufMetricExporterTests private TagList exemplarTagList; - public OtlpProtobufMetricExporterTests() + protected OtlpProtobufMetricExporterTests() { this.TagList = default; @@ -95,6 +95,8 @@ public OtlpProtobufMetricExporterTests() this.exemplarTagList.Add(new("zfilteredKey1", "zfilteredValue1")); } + protected abstract bool PrefixBufferWithUInt32LittleEndianLength { get; } + [Theory] [InlineData("longcounter", 123L, null, true, true, true, true)] [InlineData("longcounter", 123L, null, true, true, true, false)] @@ -238,17 +240,19 @@ public void CounterSerializationSingleMetricPoint(string instrumentName, long? l var testTransport = new TestTransport(); - ConnectionStringBuilder connectionStringBuilder = new ConnectionStringBuilder($"Account={expectedAccount};Namespace={expectedNamespace}"); - - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, addAccountAndNamespace ? connectionStringBuilder : null, addPrepopulatedDimensions ? prepopulatedMetricDimensions : null); + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + addAccountAndNamespace ? expectedAccount : null, + addAccountAndNamespace ? expectedNamespace : null, + addPrepopulatedDimensions ? prepopulatedMetricDimensions : null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, meterProvider.GetResource(), new Batch(exportedItems.ToArray(), exportedItems.Count)); Assert.Single(testTransport.ExportedItems); - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[0]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[0]); Assert.Single(request.ResourceMetrics); @@ -410,7 +414,13 @@ public void CounterSerializationMultipleMetricPoints(string instrumentName, long var buffer = new byte[65360]; var testTransport = new TestTransport(); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, null, null); + + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + metricsAccount: null, + metricsNamespace: null, + prepopulatedMetricDimensions: null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, Resource.Empty, new Batch(exportedItems.ToArray(), exportedItems.Count)); @@ -421,9 +431,8 @@ public void CounterSerializationMultipleMetricPoints(string instrumentName, long for (int i = 0; i < expectedMetricPoints; i++) { - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[i]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[i]); Assert.Single(request.ResourceMetrics); @@ -535,16 +544,20 @@ public void UpdownCounterSerializationSingleMetricPoint(string instrumentName, l var buffer = new byte[65360]; var testTransport = new TestTransport(); - ConnectionStringBuilder connectionStringBuilder = new ConnectionStringBuilder($"Account={expectedAccount};Namespace={expectedNamespace}"); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, addAccountAndNamespace ? connectionStringBuilder : null, addPrepopulatedDimensions ? prepopulatedMetricDimensions : null); + + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + addAccountAndNamespace ? expectedAccount : null, + addAccountAndNamespace ? expectedNamespace : null, + addPrepopulatedDimensions ? prepopulatedMetricDimensions : null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, meterProvider.GetResource(), new Batch(exportedItems.ToArray(), exportedItems.Count)); Assert.Single(testTransport.ExportedItems); - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[0]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[0]); Assert.Single(request.ResourceMetrics); @@ -656,7 +669,13 @@ public void UpdownCounterSerializationMultipleMetricPoints(string instrumentName var buffer = new byte[65360]; var testTransport = new TestTransport(); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, null, null); + + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + metricsAccount: null, + metricsNamespace: null, + prepopulatedMetricDimensions: null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, Resource.Empty, new Batch(exportedItems.ToArray(), exportedItems.Count)); @@ -667,9 +686,8 @@ public void UpdownCounterSerializationMultipleMetricPoints(string instrumentName for (int i = 0; i < expectedMetricPoints; i++) { - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[i]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[i]); Assert.Single(request.ResourceMetrics); @@ -811,16 +829,20 @@ public void HistogramSerializationSingleMetricPoint(double doubleValue, bool add var buffer = new byte[65360]; var testTransport = new TestTransport(); - ConnectionStringBuilder connectionStringBuilder = new ConnectionStringBuilder($"Account={expectedAccount};Namespace={expectedNamespace}"); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, addAccountAndNamespace ? connectionStringBuilder : null, addPrepopulatedDimensions ? prepopulatedMetricDimensions : null); + + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + addAccountAndNamespace ? expectedAccount : null, + addAccountAndNamespace ? expectedNamespace : null, + addPrepopulatedDimensions ? prepopulatedMetricDimensions : null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, meterProvider.GetResource(), new Batch(exportedItems.ToArray(), exportedItems.Count)); Assert.Single(testTransport.ExportedItems); - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[0]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[0]); Assert.Single(request.ResourceMetrics); @@ -982,7 +1004,13 @@ public void HistogramSerializationMultipleMetricPoints(double[] doubleValues) var buffer = new byte[65360]; var testTransport = new TestTransport(); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, null, null); + + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + metricsAccount: null, + metricsNamespace: null, + prepopulatedMetricDimensions: null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, meterProvider.GetResource(), new Batch(exportedItems.ToArray(), exportedItems.Count)); @@ -992,9 +1020,8 @@ public void HistogramSerializationMultipleMetricPoints(double[] doubleValues) for (int i = 0; i < expectedMetricPointCount; i++) { - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[i]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[i]); Assert.NotNull(request.ResourceMetrics[0].Resource); @@ -1133,16 +1160,20 @@ public void GaugeSerializationSingleMetricPoint(string instrumentName, long? lon var buffer = new byte[65360]; var testTransport = new TestTransport(); - ConnectionStringBuilder connectionStringBuilder = new ConnectionStringBuilder($"Account={expectedAccount};Namespace={expectedNamespace}"); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, addAccountAndNamespace ? connectionStringBuilder : null, addPrepopulatedDimensions ? prepopulatedMetricDimensions : null); + + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + addAccountAndNamespace ? expectedAccount : null, + addAccountAndNamespace ? expectedNamespace : null, + addPrepopulatedDimensions ? prepopulatedMetricDimensions : null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, meterProvider.GetResource(), new Batch(exportedItems.ToArray(), exportedItems.Count)); Assert.Single(testTransport.ExportedItems); - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[0]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[0]); Assert.Single(request.ResourceMetrics); @@ -1264,7 +1295,13 @@ public void GaugeSerializationMultipleMetricPoints(string instrumentName, long[] var buffer = new byte[65360]; var testTransport = new TestTransport(); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, null, null); + + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + metricsAccount: null, + metricsNamespace: null, + prepopulatedMetricDimensions: null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, Resource.Empty, new Batch(exportedItems.ToArray(), exportedItems.Count)); @@ -1275,9 +1312,8 @@ public void GaugeSerializationMultipleMetricPoints(string instrumentName, long[] for (int i = 0; i < expectedMetricPoints; i++) { - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[i]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[i]); Assert.Single(request.ResourceMetrics); @@ -1424,16 +1460,19 @@ public void ExponentialHistogramSerializationSingleMetricPoint(double? doubleVal var testTransport = new TestTransport(); - ConnectionStringBuilder connectionStringBuilder = new ConnectionStringBuilder($"Account={expectedAccount};Namespace={expectedNamespace}"); - var otlpProtobufSerializer = new OtlpProtobufSerializer(testTransport, addAccountAndNamespace ? connectionStringBuilder : null, addPrepopulatedDimensions ? prepopulatedMetricDimensions : null); + var otlpProtobufSerializer = new OtlpProtobufSerializer( + testTransport, + addAccountAndNamespace ? expectedAccount : null, + addAccountAndNamespace ? expectedNamespace : null, + addPrepopulatedDimensions ? prepopulatedMetricDimensions : null, + prefixBufferWithUInt32LittleEndianLength: this.PrefixBufferWithUInt32LittleEndianLength); otlpProtobufSerializer.SerializeAndSendMetrics(buffer, meterProvider.GetResource(), new Batch(exportedItems.ToArray(), exportedItems.Count)); Assert.Single(testTransport.ExportedItems); - var request = new OtlpCollector.ExportMetricsServiceRequest(); - - request.MergeFrom(testTransport.ExportedItems[0]); + var request = this.AssertAndConvertExportedBlobToRequest( + testTransport.ExportedItems[0]); Assert.Single(request.ResourceMetrics); @@ -1623,6 +1662,25 @@ private static void AssertOtlpAttributeValue(object expected, OtlpCommon.AnyValu } } + private OtlpCollector.ExportMetricsServiceRequest AssertAndConvertExportedBlobToRequest( + byte[] blob) + { + var request = new OtlpCollector.ExportMetricsServiceRequest(); + + if (this.PrefixBufferWithUInt32LittleEndianLength) + { + var content = blob.AsSpan().Slice(4); + Assert.Equal((uint)content.Length, BitConverter.ToUInt32(blob, 0)); + request.MergeFrom(content); + } + else + { + request.MergeFrom(blob); + } + + return request; + } + private class TestTransport : IMetricDataTransport { public List ExportedItems = new(); diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithPrefixTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithPrefixTests.cs new file mode 100644 index 0000000000..9421c8de95 --- /dev/null +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithPrefixTests.cs @@ -0,0 +1,12 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Xunit; + +namespace OpenTelemetry.Exporter.Geneva.Tests; + +[Collection("OtlpProtobufMetricExporterTests")] +public class OtlpProtobufMetricExporterWithPrefixTests : OtlpProtobufMetricExporterTests +{ + protected override bool PrefixBufferWithUInt32LittleEndianLength => true; +} diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithoutPrefixTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithoutPrefixTests.cs new file mode 100644 index 0000000000..f21059620f --- /dev/null +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/OtlpProtobufMetricExporterWithoutPrefixTests.cs @@ -0,0 +1,12 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using Xunit; + +namespace OpenTelemetry.Exporter.Geneva.Tests; + +[Collection("OtlpProtobufMetricExporterTests")] +public class OtlpProtobufMetricExporterWithoutPrefixTests : OtlpProtobufMetricExporterTests +{ + protected override bool PrefixBufferWithUInt32LittleEndianLength => false; +}