Skip to content

[Exporter.OTLP] Optimize GetHeaders by Spans #6179

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cda3c88
Refactor code to use Span<T> for header parsing, eliminating Split me…
nimanikoo Mar 8, 2025
65e0744
Fix formatting issues in OtlpExporterOptionsExtensions.cs
nimanikoo Mar 9, 2025
e490141
Fixup indents
nimanikoo Mar 9, 2025
511c8cb
Fix SA1513
nimanikoo Mar 9, 2025
496228b
Fix build issues
Kielek Mar 11, 2025
d73cc15
Update src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterO…
nimanikoo Mar 11, 2025
3820974
Merge branch 'main' into main_ImproveMemoryAllocation
Kielek Mar 11, 2025
bb61cd6
Update OtlpExporterOptionsExtensions.cs
nimanikoo Mar 11, 2025
094907a
Merge branch 'main' into main_ImproveMemoryAllocation
Kielek Mar 12, 2025
9092cac
Merge branch 'main' into main_ImproveMemoryAllocation
nimanikoo Mar 15, 2025
3307420
test: add new unit tests for GetHeaders method in OtlpExporterOptions
nimanikoo Mar 16, 2025
4702628
Merge branch 'main' into main_ImproveMemoryAllocation
rajkumar-rangaraj Mar 17, 2025
ef32c4c
tests: ​Refactor tests by extracting common header verification logic…
nimanikoo Mar 18, 2025
d3cd4c0
test : fix error SA1101 and error for nullable
nimanikoo Mar 18, 2025
e4789ed
Merge branch 'main' into main_ImproveMemoryAllocation
rajkumar-rangaraj Mar 18, 2025
f99de7a
Merge branch 'main' into main_ImproveMemoryAllocation
rajkumar-rangaraj Mar 18, 2025
241c93d
tests : rm unnecessary param on VerifyHeaders method
nimanikoo Mar 18, 2025
e57b21d
tests : Refactor header tests to reduce from 4 to 2 by merging valid …
nimanikoo Mar 19, 2025
4e66329
tests: Rm Unused params on header tests
nimanikoo Mar 19, 2025
c245c54
Merge branch 'main' into main_ImproveMemoryAllocation
rajkumar-rangaraj Mar 20, 2025
32c58df
Merge branch 'main' into main_ImproveMemoryAllocation
rajkumar-rangaraj Mar 21, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,33 @@ public static THeaders GetHeaders<THeaders>(this OtlpExporterOptions options, Ac
{
// According to the specification, URL-encoded headers must be supported.
optionHeaders = Uri.UnescapeDataString(optionHeaders);
ReadOnlySpan<char> headersSpan = optionHeaders.AsSpan();

Array.ForEach(
optionHeaders.Split(','),
(pair) =>
while (!headersSpan.IsEmpty)
{
int commaIndex = headersSpan.IndexOf(',');
ReadOnlySpan<char> pair;
if (commaIndex == -1)
{
// Specify the maximum number of substrings to return to 2
// This treats everything that follows the first `=` in the string as the value to be added for the metadata key
var keyValueData = pair.Split(['='], 2);
if (keyValueData.Length != 2)
{
throw new ArgumentException("Headers provided in an invalid format.");
}
pair = headersSpan;
headersSpan = ReadOnlySpan<char>.Empty;
}
else
{
pair = headersSpan.Slice(0, commaIndex);
headersSpan = headersSpan.Slice(commaIndex + 1);
}

var key = keyValueData[0].Trim();
var value = keyValueData[1].Trim();
addHeader(headers, key, value);
});
int equalIndex = pair.IndexOf('=');
if (equalIndex == -1)
{
throw new ArgumentException("Headers provided in an invalid format.");
}

var key = pair.Slice(0, equalIndex).Trim().ToString();
var value = pair.Slice(equalIndex + 1).Trim().ToString();
addHeader(headers, key, value);
}
}

foreach (var header in OtlpExporterOptions.StandardHeaders)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@ public void GetHeaders_NoOptionHeaders_ReturnsStandardHeaders(string? optionHead
}
}

[Theory]
[InlineData(" ")]
[InlineData(",key1=value1,key2=value2,")]
[InlineData(",,key1=value1,,key2=value2,,")]
[InlineData("key1")]
public void GetHeaders_InvalidOptionHeaders_ThrowsArgumentException(string inputOptionHeaders)
{
this.VerifyHeaders(inputOptionHeaders, string.Empty, true);
}

[Theory]
[InlineData("", "")]
[InlineData("key1=value1", "key1=value1")]
[InlineData("key1=value1,key2=value2", "key1=value1,key2=value2")]
[InlineData("key1=value1,key2=value2,key3=value3", "key1=value1,key2=value2,key3=value3")]
[InlineData(" key1 = value1 , key2=value2 ", "key1=value1,key2=value2")]
[InlineData("key1= value with spaces ,key2=another value", "key1=value with spaces,key2=another value")]
[InlineData("=value1", "=value1")]
[InlineData("key1=", "key1=")]
[InlineData("key1=value1%2Ckey2=value2", "key1=value1,key2=value2")]
[InlineData("key1=value1%2Ckey2=value2%2Ckey3=value3", "key1=value1,key2=value2,key3=value3")]
public void GetHeaders_ValidAndUrlEncodedHeaders_ReturnsCorrectHeaders(string inputOptionHeaders, string expectedNormalizedOptional)
{
this.VerifyHeaders(inputOptionHeaders, expectedNormalizedOptional);
}

[Theory]
#if NET462_OR_GREATER
[InlineData(OtlpExportProtocol.Grpc, typeof(GrpcExportClient))]
Expand Down Expand Up @@ -132,4 +158,50 @@ private static void AssertTransmissionHandler(OtlpExporterTransmissionHandler tr

Assert.Equal(expectedTimeoutMilliseconds, transmissionHandler.TimeoutMilliseconds);
}

/// <summary>
/// Validates whether the `Headers` property in `OtlpExporterOptions` is correctly processed and parsed.
/// It also verifies that the extracted headers match the expected values and checks for expected exceptions.
/// </summary>
/// <param name="inputOptionHeaders">The raw header string assigned to `OtlpExporterOptions`.
/// The format should be "key1=value1,key2=value2" (comma-separated key-value pairs).</param>
/// <param name="expectedNormalizedOptional">A string representing expected additional headers.
/// This will be parsed into a dictionary and compared with the actual extracted headers.</param>
/// <param name="expectException">If `true`, the method expects `GetHeaders` to throw an `ArgumentException`
/// when processing `inputOptionHeaders`.</param>
private void VerifyHeaders(string inputOptionHeaders, string expectedNormalizedOptional, bool expectException = false)
{
var options = new OtlpExporterOptions { Headers = inputOptionHeaders };

if (expectException)
{
Assert.Throws<ArgumentException>(() =>
options.GetHeaders<Dictionary<string, string>>((d, k, v) => d.Add(k, v)));
return;
}

var headers = options.GetHeaders<Dictionary<string, string>>((d, k, v) => d.Add(k, v));
var expectedOptional = new Dictionary<string, string>();

if (!string.IsNullOrEmpty(expectedNormalizedOptional))
{
foreach (var segment in expectedNormalizedOptional.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
var parts = segment.Split(new[] { '=' }, 2);
expectedOptional.Add(parts[0].Trim(), parts[1].Trim());
}
}

Assert.Equal(OtlpExporterOptions.StandardHeaders.Length + expectedOptional.Count, headers.Count);

foreach (var kvp in expectedOptional)
{
Assert.Contains(headers, h => h.Key == kvp.Key && h.Value == kvp.Value);
}

foreach (var std in OtlpExporterOptions.StandardHeaders)
{
Assert.Contains(headers, h => h.Key == std.Key && h.Value == std.Value);
}
}
}