Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7cac55d
[Prometheus.HttpListener] Improve performance
martincostello Apr 25, 2026
bdb4112
[Prometheus.HttpListener] Fix tests
martincostello Apr 25, 2026
358e2f3
[Prometheus.HttpListener] Fix test
martincostello Apr 25, 2026
4aecfea
[Prometheus.HttpListener] Address feedback
martincostello Apr 25, 2026
53d6284
[Exporter.Prometheus] Further perf improvements
martincostello Apr 26, 2026
8609727
[Exporter.Prometheus] Extend coverage
martincostello Apr 26, 2026
27a1d2e
[Exporter.Prometheus] Fix lint warnings
martincostello Apr 26, 2026
65d6b25
[Exporter.Prometheus] Extend coverage
martincostello Apr 26, 2026
fb2545d
[Exporter.Prometheus] Extend coverage
martincostello Apr 26, 2026
93d6a8a
Merge branch 'main' into PrometheusSerializer-perf
martincostello Apr 27, 2026
d6db630
[Exporter.Prometheus] Remove parameters
martincostello Apr 27, 2026
4e86902
Merge branch 'main' into PrometheusSerializer-perf
martincostello Apr 27, 2026
b0c53f1
[Exporter.Prometheus] Reduce duplication
martincostello Apr 27, 2026
21b3a09
[Exporter.Prometheus] Fix-up merge
martincostello Apr 27, 2026
140da7a
[Exporter.Prometheus] Fix tests
martincostello Apr 27, 2026
2445d2a
[Exporter.Prometheus] Reduce duplication
martincostello Apr 27, 2026
fbbbc06
[Exporter.Prometheus] Refactoring
martincostello Apr 27, 2026
c1124f4
Merge branch 'main' into PrometheusSerializer-perf
martincostello Apr 28, 2026
5fe7480
[Exporter.Prometheus] Polyfill char methods
martincostello Apr 28, 2026
29784ae
[Exporter.Prometheus] Address feedback
martincostello Apr 28, 2026
b46cb19
[Exporter.Prometheus] Update comments
martincostello Apr 28, 2026
74e5259
Merge branch 'main' into PrometheusSerializer-perf
martincostello May 1, 2026
c24c7ed
[Exporter.Prometheus] Simplify test
martincostello May 1, 2026
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
1 change: 1 addition & 0 deletions OpenTelemetry.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@
<Project Path="test/OpenTelemetry.Exporter.OpenTelemetryProtocol.FuzzTests/OpenTelemetry.Exporter.OpenTelemetryProtocol.FuzzTests.csproj" />
<Project Path="test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj" />
<Project Path="test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj" />
<Project Path="test/OpenTelemetry.Exporter.Prometheus.HttpListener.FuzzTests/OpenTelemetry.Exporter.Prometheus.HttpListener.FuzzTests.csproj" />
<Project Path="test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj" />
<Project Path="test/OpenTelemetry.Exporter.Zipkin.Tests/OpenTelemetry.Exporter.Zipkin.Tests.csproj" />
<Project Path="test/OpenTelemetry.Extensions.Hosting.Tests/OpenTelemetry.Extensions.Hosting.Tests.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ private ExportResult OnCollect(in Batch<Metric> metrics)

break;
}
catch (IndexOutOfRangeException)
catch (Exception ex) when (ex is IndexOutOfRangeException or ArgumentException)
{
Comment thread
martincostello marked this conversation as resolved.
if (!IncreaseBufferSize(ref buffer))
{
Expand Down Expand Up @@ -281,7 +281,7 @@ private ExportResult OnCollect(in Batch<Metric> metrics)

break;
}
catch (IndexOutOfRangeException)
catch (Exception ex) when (ex is IndexOutOfRangeException or ArgumentException)
{
if (!IncreaseBufferSize(ref buffer))
{
Expand All @@ -298,7 +298,7 @@ private ExportResult OnCollect(in Batch<Metric> metrics)
cursor = PrometheusSerializer.WriteEof(buffer, cursor);
break;
}
catch (IndexOutOfRangeException)
catch (Exception ex) when (ex is IndexOutOfRangeException or ArgumentException)
{
if (!IncreaseBufferSize(ref buffer))
{
Expand Down Expand Up @@ -347,7 +347,7 @@ private int WriteTargetInfo(ref byte[] buffer)

break;
}
catch (IndexOutOfRangeException)
catch (Exception ex) when (ex is IndexOutOfRangeException or ArgumentException)
{
if (!IncreaseBufferSize(ref buffer))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#if NET
using System.Buffers;
#endif
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
Expand All @@ -21,6 +24,11 @@ internal static partial class PrometheusSerializer
private const byte ASCII_LINEFEED = 0x0A; // `\n`
#pragma warning restore SA1310 // Field name should not contain an underscore

#if NET
private static readonly SearchValues<char> UnicodeEscapeChars = SearchValues.Create("\\\n");
private static readonly SearchValues<char> LabelValueEscapeChars = SearchValues.Create("\"\\\n");
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteDouble(byte[] buffer, int cursor, double value)
{
Expand All @@ -32,10 +40,7 @@ public static int WriteDouble(byte[] buffer, int cursor, double value)
var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture);
Debug.Assert(result, $"{nameof(result)} should be true.");
Comment thread
martincostello marked this conversation as resolved.
Outdated

for (var i = 0; i < cchWritten; i++)
{
buffer[cursor++] = unchecked((byte)span[i]);
}
cursor = WriteUtf8NoEscape(buffer, cursor, span[..cchWritten]);
#else
cursor = WriteAsciiStringNoEscape(buffer, cursor, value.ToString(CultureInfo.InvariantCulture));
#endif
Expand Down Expand Up @@ -66,10 +71,7 @@ public static int WriteLong(byte[] buffer, int cursor, long value)
var result = value.TryFormat(span, out var cchWritten, "G", CultureInfo.InvariantCulture);
Debug.Assert(result, $"{nameof(result)} should be true.");

for (var i = 0; i < cchWritten; i++)
{
buffer[cursor++] = unchecked((byte)span[i]);
}
cursor = WriteUtf8NoEscape(buffer, cursor, span[..cchWritten]);
#else
Comment thread
martincostello marked this conversation as resolved.
cursor = WriteAsciiStringNoEscape(buffer, cursor, value.ToString(CultureInfo.InvariantCulture));
#endif
Expand All @@ -80,12 +82,16 @@ public static int WriteLong(byte[] buffer, int cursor, long value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteAsciiStringNoEscape(byte[] buffer, int cursor, string value)
{
#if NET
return WriteUtf8NoEscape(buffer, cursor, value.AsSpan());
#else
for (var i = 0; i < value.Length; i++)
{
buffer[cursor++] = unchecked((byte)value[i]);
}

return cursor;
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -114,6 +120,9 @@ public static int WriteUnicodeNoEscape(byte[] buffer, int cursor, ushort ordinal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteUnicodeString(byte[] buffer, int cursor, string value)
{
#if NET
return WriteEscapedUtf8String(buffer, cursor, value.AsSpan(), UnicodeEscapeChars);
#else
for (var i = 0; i < value.Length; i++)
{
var ordinal = (ushort)value[i];
Expand All @@ -134,6 +143,7 @@ public static int WriteUnicodeString(byte[] buffer, int cursor, string value)
}

return cursor;
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -170,6 +180,9 @@ ordinal is (>= 'A' and <= 'Z') or
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteLabelValue(byte[] buffer, int cursor, string value)
{
#if NET
return WriteEscapedUtf8String(buffer, cursor, value.AsSpan(), LabelValueEscapeChars);
#else
for (var i = 0; i < value.Length; i++)
{
var ordinal = (ushort)value[i];
Expand All @@ -194,6 +207,7 @@ public static int WriteLabelValue(byte[] buffer, int cursor, string value)
}

return cursor;
#endif
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -225,13 +239,7 @@ public static int WriteMetricName(byte[] buffer, int cursor, PrometheusMetric me

Debug.Assert(!string.IsNullOrWhiteSpace(name), "name was null or whitespace");

for (var i = 0; i < name.Length; i++)
{
var ordinal = (ushort)name[i];
buffer[cursor++] = unchecked((byte)ordinal);
}

return cursor;
return WriteAsciiStringNoEscape(buffer, cursor, name);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand All @@ -242,13 +250,7 @@ public static int WriteMetricMetadataName(byte[] buffer, int cursor, PrometheusM

Debug.Assert(!string.IsNullOrWhiteSpace(name), "name was null or whitespace");

for (var i = 0; i < name.Length; i++)
{
var ordinal = (ushort)name[i];
buffer[cursor++] = unchecked((byte)ordinal);
}

return cursor;
return WriteAsciiStringNoEscape(buffer, cursor, name);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down Expand Up @@ -312,14 +314,7 @@ public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric

buffer[cursor++] = unchecked((byte)' ');

// Unit name has already been escaped.
#pragma warning disable IDE0370 // Remove unnecessary suppression
for (var i = 0; i < metric.Unit!.Length; i++)
#pragma warning restore IDE0370 // Remove unnecessary suppression
{
var ordinal = (ushort)metric.Unit[i];
buffer[cursor++] = unchecked((byte)ordinal);
}
cursor = WriteAsciiStringNoEscape(buffer, cursor, metric.Unit!);

buffer[cursor++] = ASCII_LINEFEED;

Expand Down Expand Up @@ -451,6 +446,60 @@ public static int WriteTargetInfo(byte[] buffer, int cursor, Resource resource)
return cursor;
}

#if NET
private static int WriteUtf8NoEscape(byte[] buffer, int cursor, ReadOnlySpan<char> value) =>
cursor + System.Text.Encoding.UTF8.GetBytes(value, buffer.AsSpan(cursor));

private static int WriteEscapedUtf8String(byte[] buffer, int cursor, ReadOnlySpan<char> value, SearchValues<char> escapedChars)
{
while (!value.IsEmpty)
{
var escapedIndex = value.IndexOfAny(escapedChars);
var nonAsciiIndex = value.IndexOfAnyExceptInRange((char)0x00, (char)0x7F);

var specialIndex =
escapedIndex < 0 ? nonAsciiIndex
: nonAsciiIndex < 0 ? escapedIndex
: Math.Min(escapedIndex, nonAsciiIndex);

if (specialIndex < 0)
{
return WriteUtf8NoEscape(buffer, cursor, value);
}

if (specialIndex > 0)
{
cursor = WriteUtf8NoEscape(buffer, cursor, value[..specialIndex]);
value = value[specialIndex..];
}

var ordinal = (ushort)value[0];
switch (ordinal)
{
case ASCII_QUOTATION_MARK:
buffer[cursor++] = ASCII_REVERSE_SOLIDUS;
buffer[cursor++] = ASCII_QUOTATION_MARK;
break;
case ASCII_REVERSE_SOLIDUS:
buffer[cursor++] = ASCII_REVERSE_SOLIDUS;
buffer[cursor++] = ASCII_REVERSE_SOLIDUS;
break;
case ASCII_LINEFEED:
buffer[cursor++] = ASCII_REVERSE_SOLIDUS;
buffer[cursor++] = unchecked((byte)'n');
break;
default:
cursor = WriteUnicodeNoEscape(buffer, cursor, ordinal);
break;
}

value = value[1..];
}

return cursor;
}
#endif

private static string MapPrometheusType(PrometheusType type) => type switch
{
PrometheusType.Gauge => "gauge",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

<ItemGroup>
<InternalsVisibleTo Include="Benchmarks" PublicKey="$(StrongNamePublicKey)" />
<InternalsVisibleTo Include="OpenTelemetry.Exporter.Prometheus.HttpListener.FuzzTests" PublicKey="$(StrongNamePublicKey)" />
<InternalsVisibleTo Include="OpenTelemetry.Exporter.Prometheus.HttpListener.Tests" PublicKey="$(StrongNamePublicKey)" />
<InternalsVisibleTo Include="OpenTelemetry.Exporter.Prometheus.Tests" PublicKey="$(StrongNamePublicKey)" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(TargetFrameworksForTests)</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FsCheck.Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Prometheus.HttpListener\OpenTelemetry.Exporter.Prometheus.HttpListener.csproj" />
</ItemGroup>

</Project>
Loading
Loading