Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions
Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions
OpenTelemetry.Exporter.PrometheusAspNetCoreOptions
OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.DisableTimestamp.get -> bool
OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.DisableTimestamp.set -> void
OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.DisableTotalNameSuffixForCounters.get -> bool
OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.DisableTotalNameSuffixForCounters.set -> void
OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.PrometheusAspNetCoreOptions() -> void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Notes](../../RELEASENOTES.md).
* Add support for .NET 10.0.
([#6307](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6307))

* Added the possibility to disable timestamps via the `PrometheusAspNetCoreOptions`.
([#6600](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6600))

## 1.13.1-beta.1

Released 2025-Oct-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,14 @@ public int ScrapeResponseCacheDurationMilliseconds
set => this.ExporterOptions.ScrapeResponseCacheDurationMilliseconds = value;
}

/// <summary>
/// Gets or sets a value indicating whether timestamps should be disabled. Default value: <see langword="false"/>.
/// </summary>
public bool DisableTimestamp
{
get => this.ExporterOptions.DisableTimestamp;
set => this.ExporterOptions.DisableTimestamp = value;
}

internal PrometheusExporterOptions ExporterOptions { get; } = new();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
OpenTelemetry.Exporter.PrometheusHttpListenerOptions
OpenTelemetry.Exporter.PrometheusHttpListenerOptions.DisableTimestamp.get -> bool
OpenTelemetry.Exporter.PrometheusHttpListenerOptions.DisableTimestamp.set -> void
OpenTelemetry.Exporter.PrometheusHttpListenerOptions.DisableTotalNameSuffixForCounters.get -> bool
OpenTelemetry.Exporter.PrometheusHttpListenerOptions.DisableTotalNameSuffixForCounters.set -> void
OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection<string!>!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Notes](../../RELEASENOTES.md).
* Add support for .NET 10.0.
([#6307](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6307))

* Added the possibility to disable timestamps via the `PrometheusHttpListenerOptions`.
([#6600](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6600))

## 1.13.1-beta.1

Released 2025-Oct-10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ private ExportResult OnCollect(in Batch<Metric> metrics)
cursor,
metric,
this.GetPrometheusMetric(metric),
this.exporter.OpenMetricsRequested);
this.exporter.OpenMetricsRequested,
this.exporter.DisableTimestamp);

break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public PrometheusExporter(PrometheusExporterOptions options)

this.ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds;
this.DisableTotalNameSuffixForCounters = options.DisableTotalNameSuffixForCounters;
this.DisableTimestamp = options.DisableTimestamp;

this.CollectionManager = new PrometheusCollectionManager(this);
}
Expand All @@ -50,6 +51,8 @@ public PrometheusExporter(PrometheusExporterOptions options)

internal bool OpenMetricsRequested { get; set; }

internal bool DisableTimestamp { get; set; }

internal Resource Resource => this.resource ??= this.ParentProvider.GetResource();

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ public int ScrapeResponseCacheDurationMilliseconds
/// Gets or sets a value indicating whether addition of _total suffix for counter metric names is disabled. Default value: <see langword="false"/>.
/// </summary>
public bool DisableTotalNameSuffixForCounters { get; set; }

/// <summary>
/// Gets or sets a value indicating whether timestamps should be disabled. Default value: <see langword="false"/>.
/// </summary>
public bool DisableTimestamp { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static bool CanWriteMetric(Metric metric)
return true;
}

public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested = false)
public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested, bool disableTimestamp)
{
cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric, openMetricsRequested);
cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric, openMetricsRequested);
Expand Down Expand Up @@ -66,9 +66,11 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
}
}

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

cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
if (!disableTimestamp)
{
buffer[cursor++] = unchecked((byte)' ');
cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
}

buffer[cursor++] = ASCII_LINEFEED;
}
Expand Down Expand Up @@ -103,9 +105,12 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
cursor = WriteAsciiStringNoEscape(buffer, cursor, "\"} ");

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

cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
if (!disableTimestamp)
{
buffer[cursor++] = unchecked((byte)' ');
cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
}

buffer[cursor++] = ASCII_LINEFEED;
}
Expand All @@ -118,9 +123,12 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
buffer[cursor++] = unchecked((byte)' ');

cursor = WriteDouble(buffer, cursor, metricPoint.GetHistogramSum());
buffer[cursor++] = unchecked((byte)' ');

cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
if (!disableTimestamp)
{
buffer[cursor++] = unchecked((byte)' ');
cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
}

buffer[cursor++] = ASCII_LINEFEED;

Expand All @@ -132,9 +140,12 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
buffer[cursor++] = unchecked((byte)' ');

cursor = WriteLong(buffer, cursor, metricPoint.GetHistogramCount());
buffer[cursor++] = unchecked((byte)' ');

cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
if (!disableTimestamp)
{
buffer[cursor++] = unchecked((byte)' ');
cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested);
}

buffer[cursor++] = ASCII_LINEFEED;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ private static BaseExportingMetricReader BuildPrometheusHttpListenerMetricReader
{
ScrapeResponseCacheDurationMilliseconds = 0,
DisableTotalNameSuffixForCounters = options.DisableTotalNameSuffixForCounters,
DisableTimestamp = options.DisableTimestamp,
});

var reader = new BaseExportingMetricReader(exporter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public class PrometheusHttpListenerOptions
/// </summary>
public bool DisableTotalNameSuffixForCounters { get; set; }

/// <summary>
/// Gets or sets a value indicating whether timestamps should be disabled. Default value: <see langword="false"/>.
/// </summary>
public bool DisableTimestamp { get; set; }

/// <summary>
/// Gets or sets the URI (Uniform Resource Identifier) prefixes to use for the http listener.
/// Default value: <c>["http://localhost:9464/"]</c>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void WriteMetric()
int cursor = 0;
foreach (var metric in this.metrics)
{
cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric));
cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric), openMetricsRequested: false, disableTimestamp: false);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,19 @@ public Task PrometheusExporterMiddlewareIntegration_OptionsFallback()
services => services.Configure<PrometheusAspNetCoreOptions>(o => o.ScrapeEndpointPath = null));
}

[Fact]
public Task PrometheusExporterMiddlewareIntegration_OptionsViaAddPrometheusExporter()
[Theory]
[InlineData(true)]
[InlineData(false)]
public Task PrometheusExporterMiddlewareIntegration_OptionsViaAddPrometheusExporter(bool disableTimestamp)
{
return RunPrometheusExporterMiddlewareIntegrationTest(
"/metrics_from_AddPrometheusExporter",
app => app.UseOpenTelemetryPrometheusScrapingEndpoint(),
configureOptions: o => o.ScrapeEndpointPath = "/metrics_from_AddPrometheusExporter");
configureOptions: o =>
{
o.ScrapeEndpointPath = "/metrics_from_AddPrometheusExporter";
o.DisableTimestamp = disableTimestamp;
});
}

[Fact]
Expand Down Expand Up @@ -407,7 +413,9 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest(

if (!skipMetrics)
{
await VerifyAsync(beginTimestamp, endTimestamp, response, requestOpenMetrics, meterTags);
var options = new PrometheusAspNetCoreOptions();
configureOptions?.Invoke(options);
await VerifyAsync(beginTimestamp, endTimestamp, response, requestOpenMetrics, meterTags, options.DisableTimestamp);
}
else
{
Expand All @@ -419,7 +427,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest(
await host.StopAsync();
}

private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, HttpResponseMessage response, bool requestOpenMetrics, KeyValuePair<string, object?>[]? meterTags)
private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, HttpResponseMessage response, bool requestOpenMetrics, KeyValuePair<string, object?>[]? meterTags, bool disableTimestamp = false)
{
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.True(response.Content.Headers.Contains("Last-Modified"));
Expand All @@ -439,6 +447,8 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht

string content = (await response.Content.ReadAsStringAsync()).ReplaceLineEndings();

var timestampPart = disableTimestamp ? string.Empty : " (\\d+)";
var timestampPartOpenMetrics = disableTimestamp ? string.Empty : " (\\d+\\.\\d{3})";
string expected = requestOpenMetrics
? $$"""
# TYPE target info
Expand All @@ -449,14 +459,14 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht
otel_scope_info{otel_scope_name="{{MeterName}}"} 1
# TYPE counter_double_bytes counter
# UNIT counter_double_bytes bytes
counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17 (\d+\.\d{3})
counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17{{timestampPartOpenMetrics}}
# EOF

""".ReplaceLineEndings()
: $$"""
# TYPE counter_double_bytes_total counter
# UNIT counter_double_bytes_total bytes
counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17 (\d+)
counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17{{timestampPart}}
# EOF

""".ReplaceLineEndings();
Expand All @@ -465,9 +475,12 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht

Assert.True(matches.Count == 1, content);

var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty, StringComparison.Ordinal), CultureInfo.InvariantCulture);
if (!disableTimestamp)
{
var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty, StringComparison.Ordinal), CultureInfo.InvariantCulture);

Assert.True(beginTimestamp <= timestamp && timestamp <= endTimestamp, $"{beginTimestamp} {timestamp} {endTimestamp}");
Assert.True(beginTimestamp <= timestamp && timestamp <= endTimestamp, $"{beginTimestamp} {timestamp} {endTimestamp}");
}
}

private static Task<IHost> StartTestHostAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,12 @@ public void UriPrefixesInvalid()
});
}

[Fact]
public async Task PrometheusExporterHttpServerIntegration()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task PrometheusExporterHttpServerIntegration(bool disableTimestamp)
{
await RunPrometheusExporterHttpServerIntegrationTest();
await RunPrometheusExporterHttpServerIntegrationTest(disableTimestamp: disableTimestamp);
}

[Fact]
Expand All @@ -73,10 +75,12 @@ public async Task PrometheusExporterHttpServerIntegration_NoMetrics()
await RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true);
}

[Fact]
public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics()
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics(bool disableTimestamp)
{
await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty);
await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, disableTimestamp: disableTimestamp);
}

[Fact]
Expand Down Expand Up @@ -220,7 +224,7 @@ private static void TestPrometheusHttpListenerUriPrefixOptions(string[] uriPrefi
});
}

private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable<KeyValuePair<string, object>> attributes, out string address)
private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable<KeyValuePair<string, object>> attributes, out string address, bool disableTimestamp = false)
{
Random random = new Random();
int retryAttempts = 5;
Expand All @@ -242,6 +246,7 @@ private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable<KeyValu
.AddPrometheusHttpListener(options =>
{
options.UriPrefixes = [generatedAddress];
options.DisableTimestamp = disableTimestamp;
})
.Build();

Expand All @@ -258,13 +263,13 @@ private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable<KeyValu
return provider ?? throw new InvalidOperationException("HttpListener could not be started");
}

private static async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", KeyValuePair<string, object?>[]? meterTags = null)
private static async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", KeyValuePair<string, object?>[]? meterTags = null, bool disableTimestamp = false)
{
var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text", StringComparison.Ordinal);

using var meter = new Meter(MeterName, MeterVersion, meterTags);

var provider = BuildMeterProvider(meter, [], out var address);
var provider = BuildMeterProvider(meter, [], out var address, disableTimestamp);

var counterTags = new KeyValuePair<string, object?>[]
{
Expand Down Expand Up @@ -308,6 +313,8 @@ private static async Task RunPrometheusExporterHttpServerIntegrationTest(bool sk

var content = await response.Content.ReadAsStringAsync();

var timestampPart = disableTimestamp ? string.Empty : " (\\d+)";
var timestampPartOpenMetrics = disableTimestamp ? string.Empty : " (\\d+\\.\\d{3})";
var expected = requestOpenMetrics
? "# TYPE target info\n"
+ "# HELP target Target metadata\n"
Expand All @@ -317,11 +324,11 @@ private static async Task RunPrometheusExporterHttpServerIntegrationTest(bool sk
+ $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n"
+ "# TYPE counter_double_bytes counter\n"
+ "# UNIT counter_double_bytes bytes\n"
+ $"counter_double_bytes_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',{additionalTags}key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n"
+ $"counter_double_bytes_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',{additionalTags}key1='value1',key2='value2'}} 101.17{timestampPartOpenMetrics}\n"
+ "# EOF\n"
: "# TYPE counter_double_bytes_total counter\n"
+ "# UNIT counter_double_bytes_total bytes\n"
+ $"counter_double_bytes_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',{additionalTags}key1='value1',key2='value2'}} 101.17 (\\d+)\n"
+ $"counter_double_bytes_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',{additionalTags}key1='value1',key2='value2'}} 101.17{timestampPart}\n"
+ "# EOF\n";

Assert.Matches(("^" + expected + "$").Replace('\'', '"'), content);
Expand Down
Loading
Loading