Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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 @@ -23,6 +23,10 @@ Notes](../../RELEASENOTES.md).
disable scope labels in Prometheus metrics. Defaults to `true`.
([#7436](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7436))

* 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))

## 1.16.0-beta.1

Released 2026-Jun-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ private static bool TryParse(
{
// Use the oldest version if no version preference was specified
version = isOpenMetrics ? PrometheusProtocol.OpenMetricsV0 : PrometheusProtocol.PrometheusV0;
escaping = null;
}
else if (version.Major is not > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Notes](../../RELEASENOTES.md).
disable scope labels in Prometheus metrics. Defaults to `true`.
([#7436](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7436))

* 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))

## 1.16.0-beta.1

Released 2026-Jun-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ internal static PrometheusProtocol Negotiate(string? contentType)
const int SupportedProtocols = 4;
var preferences = new List<(PrometheusProtocol Protocol, double Quality)>(SupportedProtocols);

var supportedEscapingSchemes = PrometheusProtocol.SupportedEscapingSchemes;

#if NET8_0_OR_GREATER
SupportedVersions supportedVersions;
#else
SupportedVersions supportedVersions;
#endif

while (value.Length > 0)
{
Expand Down Expand Up @@ -120,6 +114,7 @@ internal static PrometheusProtocol Negotiate(string? contentType)
{
// Use the oldest version if no version preference was specified
version = isOpenMetrics ? PrometheusProtocol.OpenMetricsV0 : PrometheusProtocol.PrometheusV0;
escaping = null;
}
else if (version.Major is not > 0)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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,
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,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 @@ -213,7 +213,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 @@ -259,15 +259,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 @@ -347,7 +347,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 @@ -455,7 +455,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 @@ -472,7 +472,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 @@ -637,7 +637,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 @@ -736,7 +736,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 @@ -755,7 +755,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