Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,13 @@ indent_size = 2

[*.cshtml.cs]
dotnet_diagnostic.SA1649.severity = none

# Verify settings
[*.{received,verified}.{json,txt,xml}]
charset = utf-8-bom
end_of_line = lf
indent_size = unset
indent_style = unset
insert_final_newline = false
tab_width = unset
trim_trailing_whitespace = false
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
*.sh eol=lf

# Verify settings
*.verified.txt text eol=lf working-tree-encoding=UTF-8
*.verified.xml text eol=lf working-tree-encoding=UTF-8
*.verified.json text eol=lf working-tree-encoding=UTF-8
*.verified.bin binary
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,6 @@ rewrite.coyote.json

# Test results
TestResults/

# Verify snapshots
*.received.*
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.5" />
<PackageVersion Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
<PackageVersion Include="Verify.Xunit" Version="30.20.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="[2.8.2,)" />
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions OpenTelemetry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
.gitignore = .gitignore
CONTRIBUTING.md = CONTRIBUTING.md
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
global.json = global.json
LICENSE.TXT = LICENSE.TXT
NuGet.config = NuGet.config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Fixed an issue where OTLP export of some metrics payloads to some OTLP
receivers were rejected with an HTTP 400 response due to gRPC protocol errors.
([#6567](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6567))

## 1.13.0

Released 2025-Oct-01
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,16 +292,7 @@ private static int WriteMetric(byte[] buffer, int writePosition, Metric metric)
writePosition = ProtobufSerializer.WriteDoubleWithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Max, max);
}

foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets())
{
var bucketCount = (ulong)histogramMeasurement.BucketCount;
writePosition = ProtobufSerializer.WriteFixed64WithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Bucket_Counts, bucketCount);

if (histogramMeasurement.ExplicitBound != double.PositiveInfinity)
{
writePosition = ProtobufSerializer.WriteDoubleWithTag(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Explicit_Bounds, histogramMeasurement.ExplicitBound);
}
}
writePosition = WriteHistogramBuckets(buffer, writePosition, metricPoint.GetHistogramBuckets());

writePosition = WriteDoubleExemplars(buffer, writePosition, ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Exemplars, in metricPoint);

Expand Down Expand Up @@ -513,4 +504,77 @@ private static int WriteExemplar(byte[] buffer, int writePosition, in Exemplar e
ProtobufSerializer.WriteReservedLength(buffer, exemplarLengthPosition, writePosition - (exemplarLengthPosition + ReserveSizeForLength));
return writePosition;
}

private static int WriteHistogramBuckets(byte[] buffer, int writePosition, HistogramBuckets buckets)
{
writePosition = WriteBucketCounts(buffer, writePosition, buckets.BucketCounts);

writePosition = WriteExplicitBounds(buffer, writePosition, buckets.ExplicitBounds!);

return writePosition;

static int WriteBucketCounts(byte[] buffer, int writePosition, HistogramBuckets.HistogramBucketValues[] values)
{
int length = values.Length;

writePosition = WritePackedLength(
buffer,
writePosition,
length,
ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Bucket_Counts);

for (int i = 0; i < length; i++)
{
writePosition = ProtobufSerializer.WriteFixed64LittleEndianFormat(
buffer,
writePosition,
(ulong)values[i].SnapshotValue);
}

return writePosition;
}

static int WriteExplicitBounds(byte[] buffer, int writePosition, double[] values)
{
int length = 0;

for (int i = 0; i < values.Length; i++)
{
if (values[i] != double.PositiveInfinity)
{
length++;
}
}

if (length > 0)
{
writePosition = WritePackedLength(
buffer,
writePosition,
length,
ProtobufOtlpMetricFieldNumberConstants.HistogramDataPoint_Explicit_Bounds);

for (int i = 0; i < values.Length; i++)
{
var value = values[i];
if (value != double.PositiveInfinity)
{
writePosition = ProtobufSerializer.WriteDouble(buffer, writePosition, value);
}
}
}

return writePosition;
}

static int WritePackedLength(byte[] buffer, int writePosition, int length, int fieldNumber)
{
return ProtobufSerializer.WriteTagAndLength(
buffer,
writePosition,
length * 8,
fieldNumber,
ProtobufWireType.LEN);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ internal static int WriteDoubleWithTag(byte[] buffer, int writePosition, int fie
return writePosition;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int WriteDouble(byte[] buffer, int writePosition, double value)
=> WriteFixed64LittleEndianFormat(buffer, writePosition, (ulong)BitConverter.DoubleToInt64Bits(value));

/// <summary>
/// Computes the number of bytes required to encode a 64-bit unsigned integer in Protocol Buffers' varint format.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/OpenTelemetry/Metrics/AggregatorStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ internal void SnapshotCumulative(int indexSnapshot)
internal MetricPointsAccessor GetMetricPoints()
=> new(this.metricPoints, this.currentMetricPointBatch, this.batchSize);

// This method must only be used for testing purposes
internal void OverrideTimeRange(DateTimeOffset startTimeExclusive, DateTimeOffset endTimeInclusive)
{
this.StartTimeExclusive = startTimeExclusive;
this.EndTimeInclusive = endTimeInclusive;
Comment on lines +304 to +305
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any option to make this calls by the reflection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that if desired, but IMHO that's a bit yuck if the tests already have [InternalsVisibleTo]. Or alternatively I could just make the setters internal instead of prviate.

}

private static double[] FindDefaultHistogramBounds(in MetricStreamIdentity metricStreamIdentity)
{
if (metricStreamIdentity.Unit == "s")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using OpenTelemetry.PersistentStorage.Abstractions;
using OpenTelemetry.PersistentStorage.FileSystem;
using OpenTelemetry.Tests;
using Xunit;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
using Xunit;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Tests;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Net.Http;
#endif
using System.Net.Http.Headers;
using Xunit;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Tests;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;
using Xunit;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.Implementation.ExportClient;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Xunit;
using OtlpCollector = OpenTelemetry.Proto.Collector.Trace.V1;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
using OpenTelemetry.Resources;
using Xunit;
using OtlpCollector = OpenTelemetry.Proto.Collector.Trace.V1;
using OtlpTrace = OpenTelemetry.Proto.Trace.V1;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Metrics;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Tests;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.Implementation.Serializer;

public static class ProtobufOtlpMetricSerializerTests
{
[Fact]
public static async Task WriteMetricsData_Serializes_Metrics_Correctly()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've verified manually that if this test is added to main it fails.

{
// Arrange
var metrics = GenerateMetrics();

var attributes = new Dictionary<string, object>
{
{ "service.name", "OpenTelemetry-DotNet" },
{ "service.version", "1.2.3" },
};

var buffer = new byte[1024];
var writePosition = 0;
var resource = new Resource(attributes);

// Act
var actual = ProtobufOtlpMetricSerializer.WriteMetricsData(
ref buffer,
writePosition,
resource,
metrics);

// Assert
Assert.NotEqual(0, actual);
Assert.True(actual > writePosition, $"The returned write position, {actual} is not greater than the initial write position, {writePosition}.");
Assert.True(actual <= buffer.Length, $"The returned write position, {actual} is beyond the bounds of the buffer, {buffer.Length}.");

using var stream = new MemoryStream();

#if NET
await stream.WriteAsync(buffer.AsMemory(0, actual));
#else
await stream.WriteAsync(buffer, 0, actual);
#endif

await Verify(stream, "bin")
.IgnoreParametersForVerified()
.UseDirectory("snapshots");
}

private static Batch<Metric> GenerateMetrics()
{
// Arrange
Batch<Metric> metrics = default;

// Create some metrics to export
using (var exported = new ManualResetEvent(false))
{
var experimentalOptions = new ExperimentalOptions();
var exporterOptions = new OtlpExporterOptions()
{
Endpoint = new($"http://localhost:4318/v1/"),
Protocol = OtlpExportProtocol.HttpProtobuf,
};

using var exporter = new DelegatingExporter<Metric>()
{
OnExportFunc = (batch) =>
{
metrics = batch;
exported.Set();
return ExportResult.Success;
},
};

var meterName = "otlp.protobuf.serialization";

var builder = Sdk.CreateMeterProviderBuilder().AddMeter(meterName);

var metricReaderOptions = new MetricReaderOptions();
metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = Timeout.Infinite;

builder.AddReader(
(serviceProvider) => OtlpMetricExporterExtensions.BuildOtlpExporterMetricReader(
serviceProvider,
exporterOptions,
metricReaderOptions,
experimentalOptions,
configureExporterInstance: (_) => exporter));

using var meterProvider = builder.Build();
using var meter = new Meter(meterName);

var counter = meter.CreateCounter<int>("counter");
counter.Add(18);

var gauge = meter.CreateGauge<int>("gauge");
gauge.Record(42);

var histogram = meter.CreateHistogram<int>("histogram");
histogram.Record(100);

Assert.True(meterProvider.ForceFlush());

Assert.NotEqual(0, metrics.Count);
}

// Scrub the timestamps for stable snapshots
var startTime = new DateTimeOffset(2025, 10, 08, 10, 20, 11, TimeSpan.Zero);
var endTime = startTime.AddSeconds(10);

foreach (var metric in metrics)
{
metric.AggregatorStore.OverrideTimeRange(startTime, endTime);
}

return metrics;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Text;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
using Xunit;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.Implementation.Serializer;

Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using OpenTelemetry.Metrics;
using OpenTelemetry.Tests;
using OpenTelemetry.Trace;
using Xunit;
using Xunit.Abstractions;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
using OpenTelemetry.Proto.Collector.Trace.V1;
using OpenTelemetry.Tests;
using OpenTelemetry.Trace;
using Xunit;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<PackageReference Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Verify.Xunit" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
using Xunit;
using OtlpCommon = OpenTelemetry.Proto.Common.V1;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using Xunit;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;

public class OtlpExportProtocolParserTests
Expand Down
Loading
Loading