Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4883775
[Exporter.Prometheus] Avoid struct copies
martincostello Jun 21, 2026
3cd1eba
[Exporter.Prometheus] Add dots and values escaping
martincostello Jun 21, 2026
977cac7
[Exporter.Prometheus] Fix sorting
martincostello Jun 21, 2026
8549b36
[Exporter.Prometheus] Address feedback
martincostello Jun 21, 2026
f42506e
[Exporter.Prometheus] Update test coverage
martincostello Jun 21, 2026
190a68b
[Exporter.Prometheus] Extend tests
martincostello Jun 21, 2026
c0844b9
[Exporter.Prometheus] Add allow-utf-8 escaping
martincostello Jun 21, 2026
7ff2f62
[Exporter.Prometheus] Update CHANGELOGs
martincostello Jun 21, 2026
d36104e
[Exporter.Prometheus] Extend test coverage
martincostello Jun 21, 2026
51aff69
[Exporter.Prometheus] Add new benchmark
martincostello Jun 21, 2026
0b7bd2b
[Exporter.Prometheus] Reduce allocations
martincostello Jun 21, 2026
7baa8e0
[Exporter.Prometheus] Wrap lines
martincostello Jun 21, 2026
3913d6d
[Exporter.Prometheus] Further reduce allocations
martincostello Jun 21, 2026
d434d3b
Merge branch 'main' into gh-7246
martincostello Jun 21, 2026
f93ac58
Merge branch 'main' into gh-7246-allow-utf-8
martincostello Jun 21, 2026
f79b46b
Merge branch 'main' into gh-7246
martincostello Jun 22, 2026
3a6995a
Merge branch 'main' into gh-7246-allow-utf-8
martincostello Jun 22, 2026
dbe44d4
Merge remote-tracking branch 'origin/main' into gh-7246
martincostello Jun 24, 2026
d80f412
Merge branch 'gh-7246' into gh-7246-allow-utf-8
martincostello Jun 24, 2026
8c8a028
[Exporter.Prometheus] Fix CHANGELOGs
martincostello Jun 24, 2026
e4bf711
[Exporter.Prometheus] Address feedback
martincostello Jun 24, 2026
f86a8c0
Merge branch 'main' into gh-7246
martincostello Jun 29, 2026
c7bbf26
[Exporter.Prometheus] Fix merge
martincostello Jun 29, 2026
3fb26e8
[Exporter.Prometheus] Address feedback
martincostello Jun 29, 2026
de68faf
[Exporter.Prometheus] Fix typo
martincostello Jun 29, 2026
249e431
Merge branch 'gh-7246' into gh-7246-allow-utf-8
martincostello Jun 29, 2026
7c2480e
[Exporter.Prometheus] Fix SA1202
martincostello Jun 29, 2026
7d946c4
Merge branch 'main' into gh-7246-allow-utf-8
martincostello Jun 30, 2026
3c9419f
[Exporter.Prometheus] Fix merge issues
martincostello Jun 30, 2026
5acc088
Merge branch 'main' into gh-7246-allow-utf-8
martincostello Jun 30, 2026
623180b
[Exporter.Prometheus] Address feedback
martincostello Jun 30, 2026
2be4a4a
Merge branch 'main' into gh-7246-allow-utf-8
martincostello Jun 30, 2026
eb3a8f0
[Exporter.Prometheus] Address feedback
martincostello Jul 2, 2026
9d8a9f3
[Exporter.Prometheus] Refactor test
martincostello Jul 2, 2026
5bfff05
[Exporter.Prometheus] Add UTF-8 label test
martincostello Jul 2, 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Notes](../../RELEASENOTES.md).
* Added a verbose-level diagnostic event for ignored metrics.
([#7429](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7429))

* Added support for the `dots` and `values` Prometheus UTF-8 name escaping
schemes when negotiated via the `Accept` header.
([#7439](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7439))

* Added support for the `allow-utf-8` Prometheus UTF-8 name escaping scheme
when negotiated via the `Accept` header.
([#TODO](https://github.com/open-telemetry/opentelemetry-dotnet/pull/TODO))
Comment thread
martincostello marked this conversation as resolved.
Outdated

## 1.16.0-beta.1

Released 2026-Jun-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,24 +248,11 @@ private static bool TryParse(

if (escapedValue == null || !supportedEscapingSchemes.Contains(escapedValue))
{
// TODO Support other escaping schemes, including at least "allow-utf-8".
// For now we treat "allow-utf-8" as if it were "underscores" to avoid fallback
// to PrometheusText0.0.4 where it would previously match to OpenMetricsText1.0.0.
// See https://github.com/open-telemetry/opentelemetry-dotnet/issues/7246.
if (string.Equals(escapedValue, PrometheusProtocol.AllowUtf8Escaping, StringComparison.Ordinal))
{
escaping = PrometheusProtocol.UnderscoresEscaping;
}
else
{
// Unsupported escaping scheme
return false;
}
}
else
{
escaping = escapedValue;
// Unsupported escaping scheme
return false;
}

escaping = escapedValue;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Notes](../../RELEASENOTES.md).
* Added a verbose-level diagnostic event for ignored metrics.
([#7429](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7429))

* Added support for the `dots` and `values` Prometheus UTF-8 name escaping
schemes when negotiated via the `Accept` header.
([#7439](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7439))

* Added support for the `allow-utf-8` Prometheus UTF-8 name escaping scheme
when negotiated via the `Accept` header.
([#TODO](https://github.com/open-telemetry/opentelemetry-dotnet/pull/TODO))
Comment thread
martincostello marked this conversation as resolved.
Outdated

## 1.16.0-beta.1

Released 2026-Jun-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,18 +165,7 @@ internal static PrometheusProtocol Negotiate(string? contentType)
var trimmed = TrimQuotes(value);
var escaping = trimmed.ToString();

if (PrometheusProtocol.SupportedEscapingSchemes.Contains(escaping))
{
return escaping;
}

// TODO Support other escaping schemes, including at least "allow-utf-8".
// For now we treat "allow-utf-8" as if it were "underscores" to avoid fallback
// to PrometheusText0.0.4 where it would previously match to OpenMetricsText1.0.0.
// See https://github.com/open-telemetry/opentelemetry-dotnet/issues/7246.
return string.Equals(escaping, PrometheusProtocol.AllowUtf8Escaping, StringComparison.Ordinal)
? PrometheusProtocol.UnderscoresEscaping
: null;
return PrometheusProtocol.SupportedEscapingSchemes.Contains(escaping) ? escaping : null;
}

private static ReadOnlySpan<char> SplitNext(ref ReadOnlySpan<char> span, char character)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

namespace OpenTelemetry.Exporter.Prometheus;

/// <summary>
/// The Prometheus UTF-8 name escaping scheme used when rendering metric and label names.
/// See https://prometheus.io/docs/instrumenting/escaping_schemes/.
/// </summary>
internal enum EscapingScheme
{
/// <summary>
/// Discouraged characters are replaced with the <c>_</c> character. This is the
/// OpenTelemetry default and also covers the legacy (pre-1.0.0) text formats which
/// have no negotiated escaping scheme.
/// </summary>
Underscores = 0,

/// <summary>
/// Dots are replaced with <c>_dot_</c> and underscores are doubled, allowing the
/// original name to be recovered by a Prometheus client.
/// </summary>
Dots,

/// <summary>
/// Names that are not valid legacy names are prefixed with <c>U__</c> and discouraged
/// characters are encoded as their hexadecimal Unicode code point.
/// </summary>
Values,

/// <summary>
/// Names are not escaped and are emitted as UTF-8. Names that are not valid legacy
/// names are written using the quoted exposition format (the name is moved inside
/// the label braces as a double-quoted string and non-legacy label names are quoted).
/// </summary>
AllowUtf8,
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public PrometheusCollectionManager(PrometheusExporter exporter)
internal Func<TimeSpan> GetElapsedTime { get; set; }

#if NET
public ValueTask<CollectionResponse> EnterCollect(PrometheusProtocol protocol)
public ValueTask<CollectionResponse> EnterCollect(in PrometheusProtocol protocol)
#else
public Task<CollectionResponse> EnterCollect(PrometheusProtocol protocol)
public Task<CollectionResponse> EnterCollect(in PrometheusProtocol protocol)
#endif
{
CollectionResponse? cachedResponse = null;
Expand Down Expand Up @@ -211,7 +211,7 @@ public Task<CollectionResponse> EnterCollect(PrometheusProtocol protocol)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ExitCollect(PrometheusProtocol protocol)
public void ExitCollect(in PrometheusProtocol protocol)
=> this.GetProtocolState(protocol).DecrementReaderCount();

#if NET
Expand Down Expand Up @@ -257,15 +257,15 @@ private void ExitGlobalLock()
=> Interlocked.Exchange(ref this.globalLockState, 0);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void IncrementReaderCount(PrometheusProtocol protocol)
private void IncrementReaderCount(in PrometheusProtocol protocol)
=> this.GetProtocolState(protocol).IncrementReaderCount();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool HasActiveReaders(PrometheusProtocol protocol)
private bool HasActiveReaders(in PrometheusProtocol protocol)
=> this.protocolStates.TryGetValue(protocol, out var state) && state.HasActiveReaders();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool WaitForReadersToComplete(PrometheusProtocol protocol)
private bool WaitForReadersToComplete(in PrometheusProtocol protocol)
{
var state = this.GetProtocolState(protocol);
var didSpin = false;
Expand Down Expand Up @@ -345,7 +345,7 @@ private ExportResult OnCollect(in Batch<Metric> metrics)
: ExportResult.Failure;
}

private bool TryWriteResponse(PrometheusProtocol protocol, PrometheusProtocolState state, in Batch<Metric> metrics)
private bool TryWriteResponse(in PrometheusProtocol protocol, PrometheusProtocolState state, in Batch<Metric> metrics)
{
try
{
Expand Down Expand Up @@ -451,7 +451,7 @@ private CollectionResult CreateCollectionResult(CollectionContext collectionCont
return new CollectionResult(responses);
}

private bool TryGetCachedResponse(PrometheusProtocol protocol, out CollectionResponse response)
private bool TryGetCachedResponse(in PrometheusProtocol protocol, out CollectionResponse response)
{
if (this.protocolStates.TryGetValue(protocol, out var state) &&
state.GeneratedAt is { } generatedAt &&
Expand All @@ -468,7 +468,7 @@ state.GeneratedAtElapsed is { } generatedAtElapsed &&
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private PrometheusProtocolState GetProtocolState(PrometheusProtocol protocol)
private PrometheusProtocolState GetProtocolState(in PrometheusProtocol protocol)
=> this.protocolStates.GetOrAdd(protocol, static _ => new());

private int WriteTargetInfo(TextFormatSerializer serializer, PrometheusProtocolState state)
Expand Down Expand Up @@ -633,7 +633,7 @@ public CollectionResult(IReadOnlyDictionary<PrometheusProtocol, CollectionRespon
this.responses = responses;
}

public bool TryGetResponse(PrometheusProtocol protocol, out CollectionResponse response)
public bool TryGetResponse(in PrometheusProtocol protocol, out CollectionResponse response)
{
if (this.responses?.TryGetValue(protocol, out response) == true)
{
Expand Down Expand Up @@ -732,7 +732,7 @@ private sealed class CollectionContext
private readonly TaskCompletionSource<CollectionResult> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
private bool frozen;

public CollectionContext(PrometheusProtocol protocol)
public CollectionContext(in PrometheusProtocol protocol)
{
this.protocols.Add(protocol);
}
Expand All @@ -751,7 +751,7 @@ public PrometheusProtocol[] FreezeProtocols()
public void SetResult(CollectionResult result)
=> this.tcs.SetResult(result);

public bool TryRegisterProtocol(PrometheusProtocol protocol, bool hasActiveReaders)
public bool TryRegisterProtocol(in PrometheusProtocol protocol, bool hasActiveReaders)
{
lock (this.gate)
{
Expand Down
Loading