From 665537c7ab0c0e2176e27f2dce9a7a616430969e Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Mon, 27 Nov 2023 21:51:05 +1100 Subject: [PATCH 01/23] Add scope info and version to Prometheus exporter --- .../PrometheusAspNetCoreOptions.cs | 9 ++++ .../Internal/PrometheusCollectionManager.cs | 40 ++++++++++++++--- .../Internal/PrometheusExporter.cs | 3 ++ .../Internal/PrometheusExporterOptions.cs | 5 +++ .../Internal/PrometheusSerializer.cs | 25 +++++++++++ .../Internal/PrometheusSerializerExt.cs | 44 +++++++++++++++++-- 6 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs index aead3765af..f2c8fca6e6 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs @@ -42,5 +42,14 @@ public int ScrapeResponseCacheDurationMilliseconds set => this.ExporterOptions.ScrapeResponseCacheDurationMilliseconds = value; } + /// + /// Gets or sets the ability to export metric scope info. Default value: true. + /// + public bool ScopeInfoEnabled + { + get => this.ExporterOptions.ScopeInfoEnabled; + set => this.ExporterOptions.ScopeInfoEnabled = value; + } + internal PrometheusExporterOptions ExporterOptions { get; } = new(); } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 4d4ef30eda..36ad4a96ca 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -182,19 +182,23 @@ private ExportResult OnCollect(Batch metrics) try { - foreach (var metric in metrics) + if (this.exporter.ScopeInfoEnabled) { - if (!PrometheusSerializer.CanWriteMetric(metric)) + var scopes = new HashSet(); + + foreach (var metric in metrics) { - continue; + if (PrometheusSerializer.CanWriteMetric(metric)) + { + scopes.Add(metric.MeterName); + } } - while (true) + foreach (var scope in scopes) { try { - cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric)); - break; + cursor = PrometheusSerializer.WriteScopeInfo(this.buffer, cursor, scope); } catch (IndexOutOfRangeException) { @@ -212,6 +216,30 @@ private ExportResult OnCollect(Batch metrics) } } + foreach (var metric in metrics) + { + if (!PrometheusSerializer.CanWriteMetric(metric)) + { + continue; + } + + while (true) + { + try + { + cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric), this.exporter.ScopeInfoEnabled); + break; + } + catch (IndexOutOfRangeException) + { + if (!this.IncreaseBufferSize()) + { + throw; + } + } + } + } + while (true) { try diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index ddc3df494c..e08c27d1bb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -38,6 +38,7 @@ public PrometheusExporter(PrometheusExporterOptions options) Guard.ThrowIfNull(options); this.ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds; + this.ScopeInfoEnabled = options.ScopeInfoEnabled; this.CollectionManager = new PrometheusCollectionManager(this); } @@ -63,6 +64,8 @@ internal Func, ExportResult> OnExport internal int ScrapeResponseCacheDurationMilliseconds { get; } + internal bool ScopeInfoEnabled { get; } + /// public override ExportResult Export(in Batch metrics) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs index 2d9a679124..cf277ecb6a 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs @@ -41,4 +41,9 @@ public int ScrapeResponseCacheDurationMilliseconds this.scrapeResponseCacheDurationMilliseconds = value; } } + + /// + /// Gets or sets the ability to export metric scope info. Default value: true. + /// + public bool ScopeInfoEnabled { get; set; } = true; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index 3bcd4998f1..b5b8a542a9 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -326,6 +326,31 @@ public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric return cursor; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteScopeInfo(byte[] buffer, int cursor, string scopeName) + { + if (string.IsNullOrEmpty(scopeName)) + { + return cursor; + } + + cursor = WriteAsciiStringNoEscape(buffer, cursor, "# TYPE otel_scope_info info"); + buffer[cursor++] = ASCII_LINEFEED; + + cursor = WriteAsciiStringNoEscape(buffer, cursor, "# HELP otel_scope_info Scope metadata"); + buffer[cursor++] = ASCII_LINEFEED; + + cursor = WriteAsciiStringNoEscape(buffer, cursor, "otel_scope_info"); + buffer[cursor++] = unchecked((byte)'{'); + cursor = WriteLabel(buffer, cursor, "otel_scope_name", scopeName); + buffer[cursor++] = unchecked((byte)'}'); + buffer[cursor++] = unchecked((byte)' '); + buffer[cursor++] = unchecked((byte)'1'); + buffer[cursor++] = ASCII_LINEFEED; + + return cursor; + } + private static string MapPrometheusType(PrometheusType type) { return type switch diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 0eb068b185..d0cd3eca3a 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,7 @@ public static bool CanWriteMetric(Metric metric) return true; } - public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric) + public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool scopeInfoEnabled) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); @@ -51,10 +51,22 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Counter and Gauge cursor = WriteMetricName(buffer, cursor, prometheusMetric); - if (tags.Count > 0) + if (tags.Count > 0 || scopeInfoEnabled) { buffer[cursor++] = unchecked((byte)'{'); + if (scopeInfoEnabled) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); + buffer[cursor++] = unchecked((byte)','); + + if (!string.IsNullOrEmpty(metric.MeterVersion)) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); + buffer[cursor++] = unchecked((byte)','); + } + } + foreach (var tag in tags) { cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); @@ -145,10 +157,22 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); - if (tags.Count > 0) + if (tags.Count > 0 || scopeInfoEnabled) { buffer[cursor++] = unchecked((byte)'{'); + if (scopeInfoEnabled) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); + buffer[cursor++] = unchecked((byte)','); + + if (!string.IsNullOrEmpty(metric.MeterVersion)) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); + buffer[cursor++] = unchecked((byte)','); + } + } + foreach (var tag in tags) { cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); @@ -171,10 +195,22 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); - if (tags.Count > 0) + if (tags.Count > 0 || scopeInfoEnabled) { buffer[cursor++] = unchecked((byte)'{'); + if (scopeInfoEnabled) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); + buffer[cursor++] = unchecked((byte)','); + + if (!string.IsNullOrEmpty(metric.MeterVersion)) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); + buffer[cursor++] = unchecked((byte)','); + } + } + foreach (var tag in tags) { cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); From 676b700427a5eb8dc2f4aa857aad50677b6c44f4 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 28 Nov 2023 08:36:35 +1100 Subject: [PATCH 02/23] Add tests and unshipped api --- .../.publicApi/PublicAPI.Unshipped.txt | 2 + .../Internal/PrometheusSerializerExt.cs | 14 ++- .../PrometheusExporterMiddlewareTests.cs | 9 ++ .../PrometheusSerializerTests.cs | 91 ++++++++++++++++++- 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt index 0dd120b12d..f904582a47 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt @@ -2,6 +2,8 @@ Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.PrometheusAspNetCoreOptions() -> void +OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.get -> bool +OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.get -> string OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeResponseCacheDurationMilliseconds.get -> int diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index d0cd3eca3a..80f46eef1e 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,7 @@ public static bool CanWriteMetric(Metric metric) return true; } - public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool scopeInfoEnabled) + public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool scopeInfoEnabled = true) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); @@ -126,6 +126,18 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); + if (scopeInfoEnabled) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); + buffer[cursor++] = unchecked((byte)','); + + if (!string.IsNullOrEmpty(metric.MeterVersion)) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); + buffer[cursor++] = unchecked((byte)','); + } + } + foreach (var tag in tags) { cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index ea38fb59cf..6fe77fefb5 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -238,6 +238,15 @@ await RunPrometheusExporterMiddlewareIntegrationTest( registerMeterProvider: false); } + [Fact] + public async Task PrometheusExporterMiddlewareIntegration_DisableExportScopeInfo() + { + await RunPrometheusExporterMiddlewareIntegrationTest( + "/metrics", + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + configureOptions: o => o.ScopeInfoEnabled = false); + } + private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string path, Action configure, diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 53617606cf..9ed3717731 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -512,8 +512,95 @@ public void ExponentialHistogramIsIgnoredForNow() Assert.False(PrometheusSerializer.CanWriteMetric(metrics[0])); } - private static int WriteMetric(byte[] buffer, int cursor, Metric metric) + [Fact] + public void ScopeInfo() + { + var buffer = new byte[85000]; + + var cursor = PrometheusSerializer.WriteScopeInfo(buffer, 0, "test_meter"); + + Assert.Matches( + ("^" + + "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + "otel_scope_info{otel_scope_name='test_meter'} 1\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void SumWithScopeInfo() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter("meter_name", "meter_version"); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var counter = meter.CreateUpDownCounter("test_updown_counter"); + counter.Add(10); + counter.Add(-11); + + provider.ForceFlush(); + + var cursor = WriteMetric(buffer, 0, metrics[0], true); + Assert.Matches( + ("^" + + "# TYPE test_updown_counter gauge\n" + + "test_updown_counter{otel_scope_name='meter_name',otel_scope_version='meter_version'} -1 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramOneDimensionWithScopeInfo() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter("meter_name", "meter_version"); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18, new KeyValuePair("x", "1")); + histogram.Record(100, new KeyValuePair("x", "1")); + + provider.ForceFlush(); + + var cursor = WriteMetric(buffer, 0, metrics[0], true); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='0'} 0 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='5'} 0 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='10'} 0 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='25'} 1 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='50'} 1 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='75'} 1 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='100'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='250'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='500'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='750'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='1000'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='2500'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='5000'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='7500'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='10000'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='\\+Inf'} 2 \\d+\n" + + "test_histogram_sum{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1'} 118 \\d+\n" + + "test_histogram_count{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1'} 2 \\d+\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool scopeInfoEnabled = false) { - return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric)); + return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), scopeInfoEnabled); } } From 0203e649d5c0553f1a632d50fb1d0decf437b843 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 28 Nov 2023 09:10:35 +1100 Subject: [PATCH 03/23] Fix tests --- .../PrometheusExporterMiddlewareTests.cs | 26 +++++++++++++------ .../PrometheusHttpListenerTests.cs | 13 ++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index 6fe77fefb5..c348cf0709 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -244,7 +244,8 @@ public async Task PrometheusExporterMiddlewareIntegration_DisableExportScopeInfo await RunPrometheusExporterMiddlewareIntegrationTest( "/metrics", app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), - configureOptions: o => o.ScopeInfoEnabled = false); + configureOptions: o => o.ScopeInfoEnabled = false, + skipScopeInfo: true); } private static async Task RunPrometheusExporterMiddlewareIntegrationTest( @@ -254,7 +255,8 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( Action validateResponse = null, bool registerMeterProvider = true, Action configureOptions = null, - bool skipMetrics = false) + bool skipMetrics = false, + bool skipScopeInfo = false) { using var host = await new HostBuilder() .ConfigureWebHost(webBuilder => webBuilder @@ -305,14 +307,22 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string content = await response.Content.ReadAsStringAsync(); + string expected = skipScopeInfo + ? "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" + + "\n" + + "# EOF\n" + : "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + + "\n" + + "# EOF\n"; + var matches = Regex.Matches( content, - ("^" - + "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" - + "\n" - + "# EOF\n" - + "$").Replace('\'', '"')); + ("^" + expected + "$").Replace('\'', '"')); Assert.Single(matches); diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index d964432009..1d27f372cd 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -133,9 +133,18 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri Assert.True(response.Content.Headers.Contains("Last-Modified")); Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + var content = await response.Content.ReadAsStringAsync(); + Assert.Matches( - "^# TYPE counter_double_total counter\ncounter_double_total{key1='value1',key2='value2'} 101.17 \\d+\n\n# EOF\n$".Replace('\'', '"'), - await response.Content.ReadAsStringAsync()); + ("^" + + "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{this.meterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{this.meterName}',key1='value1',key2='value2'}} 101.17 \\d+\n\n" + + "# EOF\n" + + "$").Replace('\'', '"'), + content); } else { From 44eaa4da687cf45b0d06ddc9cb4464cb643bc99b Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Tue, 28 Nov 2023 09:16:24 +1100 Subject: [PATCH 04/23] Add to changelog --- src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index adc7f6d701..ce254fa1c4 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +* Added support for exporting otel_scope_info tags to metrics ## 1.7.0-alpha.1 From f8ee51b4b814157f31aa07ca8508e3fb99fc0231 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Wed, 29 Nov 2023 19:55:19 +1100 Subject: [PATCH 05/23] Fix build and reuse scopes --- .../PrometheusAspNetCoreOptions.cs | 2 +- .../Internal/PrometheusCollectionManager.cs | 8 +++++--- .../Internal/PrometheusExporterOptions.cs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs index f2c8fca6e6..7f7a574683 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs @@ -43,7 +43,7 @@ public int ScrapeResponseCacheDurationMilliseconds } /// - /// Gets or sets the ability to export metric scope info. Default value: true. + /// Gets or sets a value indicating whether to export metric scope info. Default value: true. /// public bool ScopeInfoEnabled { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 36ad4a96ca..f02a0ff24f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -27,6 +27,7 @@ internal sealed class PrometheusCollectionManager private readonly int scrapeResponseCacheDurationMilliseconds; private readonly Func, ExportResult> onCollectRef; private readonly Dictionary metricsCache; + private readonly HashSet scopes; private int metricsCacheCount; private byte[] buffer = new byte[85000]; // encourage the object to live in LOH (large object heap) private int globalLockState; @@ -42,6 +43,7 @@ public PrometheusCollectionManager(PrometheusExporter exporter) this.scrapeResponseCacheDurationMilliseconds = this.exporter.ScrapeResponseCacheDurationMilliseconds; this.onCollectRef = this.OnCollect; this.metricsCache = new Dictionary(); + this.scopes = new HashSet(); } #if NET6_0_OR_GREATER @@ -184,17 +186,17 @@ private ExportResult OnCollect(Batch metrics) { if (this.exporter.ScopeInfoEnabled) { - var scopes = new HashSet(); + this.scopes.Clear(); foreach (var metric in metrics) { if (PrometheusSerializer.CanWriteMetric(metric)) { - scopes.Add(metric.MeterName); + this.scopes.Add(metric.MeterName); } } - foreach (var scope in scopes) + foreach (var scope in this.scopes) { try { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs index cf277ecb6a..05af84f8fd 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs @@ -43,7 +43,7 @@ public int ScrapeResponseCacheDurationMilliseconds } /// - /// Gets or sets the ability to export metric scope info. Default value: true. + /// Gets or sets a value indicating whether to export metric scope info. Default value: true. /// public bool ScopeInfoEnabled { get; set; } = true; } From 4ba86a8472f2fc2e880297b7995e924da9b4b273 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 30 Nov 2023 07:49:55 +1100 Subject: [PATCH 06/23] Fix build, add extra newline for scope_info --- src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md | 1 + .../Internal/PrometheusSerializer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index ce254fa1c4..09c9c02f8d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased + * Added support for exporting otel_scope_info tags to metrics ## 1.7.0-alpha.1 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index b5b8a542a9..b11aace5d5 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -347,6 +347,7 @@ public static int WriteScopeInfo(byte[] buffer, int cursor, string scopeName) buffer[cursor++] = unchecked((byte)' '); buffer[cursor++] = unchecked((byte)'1'); buffer[cursor++] = ASCII_LINEFEED; + buffer[cursor++] = ASCII_LINEFEED; return cursor; } From 6192827546811723968a4815e2d84d7f9922ce2c Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 30 Nov 2023 08:13:05 +1100 Subject: [PATCH 07/23] Fix tests --- .../PrometheusExporterMiddlewareTests.cs | 1 + .../PrometheusHttpListenerTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index c348cf0709..69b7543701 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -315,6 +315,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( : "# TYPE otel_scope_info info\n" + "# HELP otel_scope_info Scope metadata\n" + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "\n" + "# TYPE counter_double_total counter\n" + $"counter_double_total{{otel_scope_name='{MeterName}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + "\n" diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index 1d27f372cd..9966d8fca0 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -140,6 +140,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri + "# TYPE otel_scope_info info\n" + "# HELP otel_scope_info Scope metadata\n" + $"otel_scope_info{{otel_scope_name='{this.meterName}'}} 1\n" + + "\n" + "# TYPE counter_double_total counter\n" + $"counter_double_total{{otel_scope_name='{this.meterName}',key1='value1',key2='value2'}} 101.17 \\d+\n\n" + "# EOF\n" From 95266474f9c29eb5fa7cfaecf350d67cd2099470 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 30 Nov 2023 23:47:32 +1100 Subject: [PATCH 08/23] Working with prometheus now --- .../.publicApi/PublicAPI.Unshipped.txt | 4 +- .../PrometheusAspNetCoreOptions.cs | 8 +- .../PrometheusExporterMiddleware.cs | 37 +++++- .../.publicApi/PublicAPI.Unshipped.txt | 2 + .../Internal/PrometheusCollectionManager.cs | 4 +- .../Internal/PrometheusExporter.cs | 4 +- .../Internal/PrometheusExporterOptions.cs | 4 +- .../Internal/PrometheusSerializer.cs | 65 ++++++++++- .../Internal/PrometheusSerializerExt.cs | 110 ++---------------- .../PrometheusHttpListener.cs | 37 +++++- ...pListenerMeterProviderBuilderExtensions.cs | 6 +- .../PrometheusHttpListenerOptions.cs | 5 + .../PrometheusExporterMiddlewareTests.cs | 45 ++++--- .../PrometheusHttpListenerTests.cs | 54 ++++++--- .../PrometheusSerializerTests.cs | 42 +++---- 15 files changed, 259 insertions(+), 168 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt index f904582a47..50e26513d4 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt @@ -2,8 +2,8 @@ Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.PrometheusAspNetCoreOptions() -> void -OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.get -> bool -OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.set -> void +OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.OpenMetricsEnabled.get -> bool +OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.OpenMetricsEnabled.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.get -> string OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeResponseCacheDurationMilliseconds.get -> int diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs index 7f7a574683..ab47be04db 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs @@ -43,12 +43,12 @@ public int ScrapeResponseCacheDurationMilliseconds } /// - /// Gets or sets a value indicating whether to export metric scope info. Default value: true. + /// Gets or sets a value indicating whether to export OpenMetrics compatible scrape responses. Default value: true. /// - public bool ScopeInfoEnabled + public bool OpenMetricsEnabled { - get => this.ExporterOptions.ScopeInfoEnabled; - set => this.ExporterOptions.ScopeInfoEnabled = value; + get => this.ExporterOptions.OpenMetricsEnabled; + set => this.ExporterOptions.OpenMetricsEnabled = value; } internal PrometheusExporterOptions ExporterOptions { get; } = new(); diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index bfbb178783..ef42d349b0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -16,6 +16,8 @@ using System.Diagnostics; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; @@ -27,6 +29,8 @@ namespace OpenTelemetry.Exporter; /// internal sealed class PrometheusExporterMiddleware { + private const string OpenMetricsMediaType = "application/openmetrics-text"; + private readonly PrometheusExporter exporter; /// @@ -75,7 +79,14 @@ public async Task InvokeAsync(HttpContext httpContext) #else response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); #endif - response.ContentType = "text/plain; charset=utf-8; version=0.0.4"; + if (this.exporter.OpenMetricsEnabled && this.AcceptsOpenMetrics(httpContext.Request)) + { + response.ContentType = "application/openmetrics-text; version=1.0.0; charset=utf-8"; + } + else + { + response.ContentType = "text/plain; charset=utf-8; version=0.0.4"; + } await response.Body.WriteAsync(collectionResponse.View.Array, 0, collectionResponse.View.Count).ConfigureAwait(false); } @@ -102,4 +113,28 @@ public async Task InvokeAsync(HttpContext httpContext) this.exporter.OnExport = null; } + + private bool AcceptsOpenMetrics(HttpRequest request) + { + var requestAccept = request.Headers[HeaderNames.Accept]; + + if (StringValues.IsNullOrEmpty(requestAccept)) + { + return false; + } + + var acceptTypes = requestAccept.ToString().Split(','); + + foreach (var acceptType in acceptTypes) + { + var acceptSubType = acceptType.Split(';').FirstOrDefault()?.Trim(); + + if (acceptSubType == OpenMetricsMediaType) + { + return true; + } + } + + return false; + } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt index 9bc2e72461..8dbcc600a0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ OpenTelemetry.Exporter.PrometheusHttpListenerOptions +OpenTelemetry.Exporter.PrometheusHttpListenerOptions.OpenMetricsEnabled.get -> bool +OpenTelemetry.Exporter.PrometheusHttpListenerOptions.OpenMetricsEnabled.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index f02a0ff24f..4d3382a36d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -184,7 +184,7 @@ private ExportResult OnCollect(Batch metrics) try { - if (this.exporter.ScopeInfoEnabled) + if (this.exporter.OpenMetricsEnabled) { this.scopes.Clear(); @@ -229,7 +229,7 @@ private ExportResult OnCollect(Batch metrics) { try { - cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric), this.exporter.ScopeInfoEnabled); + cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric), this.exporter.OpenMetricsEnabled); break; } catch (IndexOutOfRangeException) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index e08c27d1bb..08689c74b2 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -38,7 +38,7 @@ public PrometheusExporter(PrometheusExporterOptions options) Guard.ThrowIfNull(options); this.ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds; - this.ScopeInfoEnabled = options.ScopeInfoEnabled; + this.OpenMetricsEnabled = options.OpenMetricsEnabled; this.CollectionManager = new PrometheusCollectionManager(this); } @@ -64,7 +64,7 @@ internal Func, ExportResult> OnExport internal int ScrapeResponseCacheDurationMilliseconds { get; } - internal bool ScopeInfoEnabled { get; } + internal bool OpenMetricsEnabled { get; } /// public override ExportResult Export(in Batch metrics) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs index 05af84f8fd..2e2da9c7d1 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs @@ -43,7 +43,7 @@ public int ScrapeResponseCacheDurationMilliseconds } /// - /// Gets or sets a value indicating whether to export metric scope info. Default value: true. + /// Gets or sets a value indicating whether to export OpenMetrics compatible scrape responses. Default value: true. /// - public bool ScopeInfoEnabled { get; set; } = true; + public bool OpenMetricsEnabled { get; set; } = true; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index b11aace5d5..c7d2c9886a 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -18,6 +18,7 @@ using System.Globalization; using System.Runtime.CompilerServices; using OpenTelemetry.Internal; +using OpenTelemetry.Metrics; namespace OpenTelemetry.Exporter.Prometheus; @@ -347,7 +348,69 @@ public static int WriteScopeInfo(byte[] buffer, int cursor, string scopeName) buffer[cursor++] = unchecked((byte)' '); buffer[cursor++] = unchecked((byte)'1'); buffer[cursor++] = ASCII_LINEFEED; - buffer[cursor++] = ASCII_LINEFEED; + + return cursor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool openMetricsEnabled) + { + if (openMetricsEnabled) + { + cursor = WriteLong(buffer, cursor, value / 1000); + buffer[cursor++] = unchecked((byte)'.'); + + long millis = value % 1000; + + if (millis < 100) + { + buffer[cursor++] = unchecked((byte)'0'); + } + + if (millis < 10) + { + buffer[cursor++] = unchecked((byte)'0'); + } + + return WriteLong(buffer, cursor, millis); + } + + return WriteLong(buffer, cursor, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool openMetricsEnabled, bool writeEnclosingBraces = true) + { + if (tags.Count > 0 || openMetricsEnabled) + { + if (writeEnclosingBraces) + { + buffer[cursor++] = unchecked((byte)'{'); + } + + if (openMetricsEnabled) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); + buffer[cursor++] = unchecked((byte)','); + + if (!string.IsNullOrEmpty(metric.MeterVersion)) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); + buffer[cursor++] = unchecked((byte)','); + } + } + + foreach (var tag in tags) + { + cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); + buffer[cursor++] = unchecked((byte)','); + } + + if (writeEnclosingBraces) + { + buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. + } + } return cursor; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 80f46eef1e..002c85bd47 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,7 @@ public static bool CanWriteMetric(Metric metric) return true; } - public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool scopeInfoEnabled = true) + public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsEnabled = true) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); @@ -45,36 +45,11 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe { foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { - var tags = metricPoint.Tags; var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); // Counter and Gauge cursor = WriteMetricName(buffer, cursor, prometheusMetric); - - if (tags.Count > 0 || scopeInfoEnabled) - { - buffer[cursor++] = unchecked((byte)'{'); - - if (scopeInfoEnabled) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); - buffer[cursor++] = unchecked((byte)','); - - if (!string.IsNullOrEmpty(metric.MeterVersion)) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); - buffer[cursor++] = unchecked((byte)','); - } - } - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } - - buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. - } + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsEnabled); buffer[cursor++] = unchecked((byte)' '); @@ -106,7 +81,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); buffer[cursor++] = ASCII_LINEFEED; } @@ -125,24 +100,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); - - if (scopeInfoEnabled) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); - buffer[cursor++] = unchecked((byte)','); - - if (!string.IsNullOrEmpty(metric.MeterVersion)) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); - buffer[cursor++] = unchecked((byte)','); - } - } - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } + cursor = WriteTags(buffer, cursor, metric, tags, openMetricsEnabled, writeEnclosingBraces: false); cursor = WriteAsciiStringNoEscape(buffer, cursor, "le=\""); @@ -160,7 +118,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteLong(buffer, cursor, totalCount); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); buffer[cursor++] = ASCII_LINEFEED; } @@ -168,83 +126,33 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Histogram sum cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); - - if (tags.Count > 0 || scopeInfoEnabled) - { - buffer[cursor++] = unchecked((byte)'{'); - - if (scopeInfoEnabled) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); - buffer[cursor++] = unchecked((byte)','); - - if (!string.IsNullOrEmpty(metric.MeterVersion)) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); - buffer[cursor++] = unchecked((byte)','); - } - } - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } - - buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. - } + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsEnabled); buffer[cursor++] = unchecked((byte)' '); cursor = WriteDouble(buffer, cursor, metricPoint.GetHistogramSum()); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); buffer[cursor++] = ASCII_LINEFEED; // Histogram count cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); - - if (tags.Count > 0 || scopeInfoEnabled) - { - buffer[cursor++] = unchecked((byte)'{'); - - if (scopeInfoEnabled) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); - buffer[cursor++] = unchecked((byte)','); - - if (!string.IsNullOrEmpty(metric.MeterVersion)) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); - buffer[cursor++] = unchecked((byte)','); - } - } - - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } - - buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. - } + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsEnabled); buffer[cursor++] = unchecked((byte)' '); cursor = WriteLong(buffer, cursor, metricPoint.GetHistogramCount()); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteLong(buffer, cursor, timestamp); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); buffer[cursor++] = ASCII_LINEFEED; } } - buffer[cursor++] = ASCII_LINEFEED; - return cursor; } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index 112bbf2620..5dc3e2f607 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System.Collections.Specialized; using System.Net; using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Internal; @@ -22,6 +23,8 @@ namespace OpenTelemetry.Exporter; internal sealed class PrometheusHttpListener : IDisposable { + private const string OpenMetricsMediaType = "application/openmetrics-text"; + private readonly PrometheusExporter exporter; private readonly HttpListener httpListener = new(); private readonly object syncObject = new(); @@ -156,7 +159,15 @@ private async Task ProcessRequestAsync(HttpListenerContext context) { context.Response.StatusCode = 200; context.Response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); - context.Response.ContentType = "text/plain; charset=utf-8; version=0.0.4"; + + if (this.AcceptsOpenMetrics(context.Request.Headers)) + { + context.Response.ContentType = "application/openmetrics-text; version=1.0.0; charset=utf-8"; + } + else + { + context.Response.ContentType = "text/plain; charset=utf-8; version=0.0.4"; + } await context.Response.OutputStream.WriteAsync(collectionResponse.View.Array, 0, collectionResponse.View.Count).ConfigureAwait(false); } @@ -187,4 +198,28 @@ private async Task ProcessRequestAsync(HttpListenerContext context) { } } + + private bool AcceptsOpenMetrics(NameValueCollection headers) + { + var requestAccept = headers["Accept"]; + + if (string.IsNullOrEmpty(requestAccept)) + { + return false; + } + + var acceptTypes = requestAccept.Split(','); + + foreach (var acceptType in acceptTypes) + { + var acceptSubType = acceptType.Split(';').FirstOrDefault()?.Trim(); + + if (acceptSubType == OpenMetricsMediaType) + { + return true; + } + } + + return false; + } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs index 62c5f386b0..b7f6ea804b 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -78,7 +78,11 @@ public static MeterProviderBuilder AddPrometheusHttpListener( private static MetricReader BuildPrometheusHttpListenerMetricReader( PrometheusHttpListenerOptions options) { - var exporter = new PrometheusExporter(new PrometheusExporterOptions { ScrapeResponseCacheDurationMilliseconds = 0 }); + var exporter = new PrometheusExporter(new PrometheusExporterOptions + { + ScrapeResponseCacheDurationMilliseconds = 0, + OpenMetricsEnabled = options.OpenMetricsEnabled, + }); var reader = new BaseExportingMetricReader(exporter) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs index 51954fe15d..ebc265de5f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs @@ -30,6 +30,11 @@ public class PrometheusHttpListenerOptions /// public string ScrapeEndpointPath { get; set; } = "/metrics"; + /// + /// Gets or sets a value indicating whether to export OpenMetrics compatible scrape responses. Default value: true. + /// + public bool OpenMetricsEnabled { get; set; } = true; + /// /// Gets or sets the URI (Uniform Resource Identifier) prefixes to use for the http listener. /// Default value: ["http://localhost:9464/"]. diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index 69b7543701..f147d2ed07 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -17,6 +17,7 @@ #if !NETFRAMEWORK using System.Diagnostics.Metrics; using System.Net; +using System.Net.Http.Headers; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -239,13 +240,13 @@ await RunPrometheusExporterMiddlewareIntegrationTest( } [Fact] - public async Task PrometheusExporterMiddlewareIntegration_DisableExportScopeInfo() + public async Task PrometheusExporterMiddlewareIntegration_DisableOpenMetrics() { await RunPrometheusExporterMiddlewareIntegrationTest( "/metrics", app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), - configureOptions: o => o.ScopeInfoEnabled = false, - skipScopeInfo: true); + configureOptions: o => o.OpenMetricsEnabled = false, + useOpenMetrics: false); } private static async Task RunPrometheusExporterMiddlewareIntegrationTest( @@ -256,7 +257,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( bool registerMeterProvider = true, Action configureOptions = null, bool skipMetrics = false, - bool skipScopeInfo = false) + bool useOpenMetrics = true) { using var host = await new HostBuilder() .ConfigureWebHost(webBuilder => webBuilder @@ -295,7 +296,14 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( counter.Add(0.99D, tags); } - using var response = await host.GetTestClient().GetAsync(path); + using var client = host.GetTestClient(); + + if (useOpenMetrics) + { + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/openmetrics-text")); + } + + using var response = await client.GetAsync(path); var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -303,22 +311,27 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Content.Headers.Contains("Last-Modified")); - Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + + if (useOpenMetrics) + { + Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType.ToString()); + } + else + { + Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + } string content = await response.Content.ReadAsStringAsync(); - string expected = skipScopeInfo - ? "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" - + "\n" - + "# EOF\n" - : "# TYPE otel_scope_info info\n" + string expected = useOpenMetrics + ? "# TYPE otel_scope_info info\n" + "# HELP otel_scope_info Scope metadata\n" + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" - + "\n" + "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" - + "\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + + "# EOF\n" + : "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" + "# EOF\n"; var matches = Regex.Matches( @@ -327,7 +340,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( Assert.Single(matches); - var timestamp = long.Parse(matches[0].Groups[1].Value); + var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty)); Assert.True(beginTimestamp <= timestamp && timestamp <= endTimestamp); } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index 9966d8fca0..183b77bc52 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -16,6 +16,7 @@ using System.Diagnostics.Metrics; using System.Net; +using System.Net.Http.Headers; #if NETFRAMEWORK using System.Net.Http; #endif @@ -90,7 +91,13 @@ public async Task PrometheusExporterHttpServerIntegration_NoMetrics() await this.RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true); } - private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false) + [Fact] + public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics() + { + await this.RunPrometheusExporterHttpServerIntegrationTest(useOpenMetrics: false); + } + + private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, bool useOpenMetrics = true) { Random random = new Random(); int retryAttempts = 5; @@ -107,7 +114,11 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { address }) + .AddPrometheusHttpListener(options => + { + options.OpenMetricsEnabled = useOpenMetrics; + options.UriPrefixes = new string[] { address }; + }) .Build(); } @@ -125,27 +136,42 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri } using HttpClient client = new HttpClient(); + + if (useOpenMetrics) + { + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/openmetrics-text")); + } + using var response = await client.GetAsync($"{address}metrics"); if (!skipMetrics) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(response.Content.Headers.Contains("Last-Modified")); - Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + + if (useOpenMetrics) + { + Assert.Equal("application/openmetrics-text; version=1.0.0; charset=utf-8", response.Content.Headers.ContentType.ToString()); + } + else + { + Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType.ToString()); + } var content = await response.Content.ReadAsStringAsync(); - Assert.Matches( - ("^" - + "# TYPE otel_scope_info info\n" - + "# HELP otel_scope_info Scope metadata\n" - + $"otel_scope_info{{otel_scope_name='{this.meterName}'}} 1\n" - + "\n" - + "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{this.meterName}',key1='value1',key2='value2'}} 101.17 \\d+\n\n" - + "# EOF\n" - + "$").Replace('\'', '"'), - content); + var expected = useOpenMetrics + ? "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{this.meterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{this.meterName}',key1='value1',key2='value2'}} 101.17 \\d+\\.\\d{{3}}\n" + + "# EOF\n" + : "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 \\d+\n" + + "# EOF\n"; + + Assert.Matches(("^" + expected + "$").Replace('\'', '"'), content); } else { diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 9ed3717731..23990dc9cb 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -550,7 +550,7 @@ public void SumWithScopeInfo() Assert.Matches( ("^" + "# TYPE test_updown_counter gauge\n" - + "test_updown_counter{otel_scope_name='meter_name',otel_scope_version='meter_version'} -1 \\d+\n" + + "test_updown_counter{otel_scope_name='meter_name',otel_scope_version='meter_version'} -1 \\d+\\.\\d{3}\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -577,30 +577,30 @@ public void HistogramOneDimensionWithScopeInfo() Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='0'} 0 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='5'} 0 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='10'} 0 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='25'} 1 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='50'} 1 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='75'} 1 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='100'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='250'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='500'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='750'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='1000'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='2500'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='5000'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='7500'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='10000'} 2 \\d+\n" - + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='\\+Inf'} 2 \\d+\n" - + "test_histogram_sum{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1'} 118 \\d+\n" - + "test_histogram_count{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1'} 2 \\d+\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='0'} 0 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='5'} 0 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='10'} 0 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='25'} 1 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='50'} 1 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='75'} 1 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='100'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='250'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='500'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='750'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='1000'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='2500'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='5000'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='7500'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='10000'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_bucket{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1',le='\\+Inf'} 2 \\d+\\.\\d{3}\n" + + "test_histogram_sum{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1'} 118 \\d+\\.\\d{3}\n" + + "test_histogram_count{otel_scope_name='meter_name',otel_scope_version='meter_version',x='1'} 2 \\d+\\.\\d{3}\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } - private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool scopeInfoEnabled = false) + private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool useOpenMetrics = false) { - return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), scopeInfoEnabled); + return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics); } } From 937b7dadd7fc25fa33e67cd2b7e87e353e0a55a9 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Fri, 1 Dec 2023 08:50:17 +1100 Subject: [PATCH 09/23] Modify output depending on accept headers --- .../PrometheusExporterMiddleware.cs | 9 +++++++-- .../Internal/PrometheusCollectionManager.cs | 19 ++++++++++++++----- .../Internal/PrometheusExporter.cs | 2 ++ .../Internal/PrometheusSerializer.cs | 4 ++-- .../Internal/PrometheusSerializerExt.cs | 10 +++++----- .../PrometheusHttpListener.cs | 7 +++++-- .../PrometheusCollectionManagerTests.cs | 6 +++--- .../PrometheusSerializerTests.cs | 2 +- 8 files changed, 39 insertions(+), 20 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index ef42d349b0..4ac1c640f5 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -68,7 +68,11 @@ public async Task InvokeAsync(HttpContext httpContext) try { - var collectionResponse = await this.exporter.CollectionManager.EnterCollect().ConfigureAwait(false); + var openMetricsRequested = + this.exporter.OpenMetricsEnabled && this.AcceptsOpenMetrics(httpContext.Request); + + var collectionResponse = await this.exporter.CollectionManager.EnterCollect(openMetricsRequested).ConfigureAwait(false); + try { if (collectionResponse.View.Count > 0) @@ -79,7 +83,8 @@ public async Task InvokeAsync(HttpContext httpContext) #else response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); #endif - if (this.exporter.OpenMetricsEnabled && this.AcceptsOpenMetrics(httpContext.Request)) + + if (openMetricsRequested) { response.ContentType = "application/openmetrics-text; version=1.0.0; charset=utf-8"; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 4d3382a36d..2529db47d0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -47,9 +47,9 @@ public PrometheusCollectionManager(PrometheusExporter exporter) } #if NET6_0_OR_GREATER - public ValueTask EnterCollect() + public ValueTask EnterCollect(bool openMetricsRequested) #else - public Task EnterCollect() + public Task EnterCollect(bool openMetricsRequested) #endif { this.EnterGlobalLock(); @@ -95,7 +95,7 @@ public Task EnterCollect() this.ExitGlobalLock(); CollectionResponse response; - var result = this.ExecuteCollect(); + var result = this.ExecuteCollect(openMetricsRequested); if (result) { this.previousDataViewGeneratedAtUtc = DateTime.UtcNow; @@ -170,11 +170,13 @@ private void WaitForReadersToComplete() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool ExecuteCollect() + private bool ExecuteCollect(bool openMetricsRequested) { this.exporter.OnExport = this.onCollectRef; + this.exporter.OpenMetricsRequested = openMetricsRequested; var result = this.exporter.Collect(Timeout.Infinite); this.exporter.OnExport = null; + this.exporter.OpenMetricsRequested = null; return result; } @@ -229,7 +231,14 @@ private ExportResult OnCollect(Batch metrics) { try { - cursor = PrometheusSerializer.WriteMetric(this.buffer, cursor, metric, this.GetPrometheusMetric(metric), this.exporter.OpenMetricsEnabled); + cursor = PrometheusSerializer.WriteMetric( + this.buffer, + cursor, + metric, + this.GetPrometheusMetric(metric), + this.exporter.OpenMetricsEnabled, + this.exporter.OpenMetricsRequested ?? false); + break; } catch (IndexOutOfRangeException) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index 08689c74b2..8a62eca6c9 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -58,6 +58,8 @@ internal Func, ExportResult> OnExport set => this.funcExport = value; } + internal bool? OpenMetricsRequested { get; set; } + internal Action OnDispose { get; set; } internal PrometheusCollectionManager CollectionManager { get; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index c7d2c9886a..4ef1506cdb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -353,9 +353,9 @@ public static int WriteScopeInfo(byte[] buffer, int cursor, string scopeName) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool openMetricsEnabled) + public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool useOpenMetrics) { - if (openMetricsEnabled) + if (useOpenMetrics) { cursor = WriteLong(buffer, cursor, value / 1000); buffer[cursor++] = unchecked((byte)'.'); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 002c85bd47..21c4d115d4 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,7 @@ public static bool CanWriteMetric(Metric metric) return true; } - public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsEnabled = true) + public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsEnabled = true, bool openMetricsRequested = false) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); @@ -81,7 +81,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe buffer[cursor++] = unchecked((byte)' '); - cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; } @@ -118,7 +118,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteLong(buffer, cursor, totalCount); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; } @@ -133,7 +133,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteDouble(buffer, cursor, metricPoint.GetHistogramSum()); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; @@ -147,7 +147,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteLong(buffer, cursor, metricPoint.GetHistogramCount()); buffer[cursor++] = unchecked((byte)' '); - cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsEnabled); + cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); buffer[cursor++] = ASCII_LINEFEED; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index 5dc3e2f607..e2944c4587 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -151,7 +151,10 @@ private async Task ProcessRequestAsync(HttpListenerContext context) { try { - var collectionResponse = await this.exporter.CollectionManager.EnterCollect().ConfigureAwait(false); + var openMetricsRequested = this.exporter.OpenMetricsEnabled && this.AcceptsOpenMetrics(context.Request.Headers); + + var collectionResponse = await this.exporter.CollectionManager.EnterCollect(openMetricsRequested).ConfigureAwait(false); + try { context.Response.Headers.Add("Server", string.Empty); @@ -160,7 +163,7 @@ private async Task ProcessRequestAsync(HttpListenerContext context) context.Response.StatusCode = 200; context.Response.Headers.Add("Last-Modified", collectionResponse.GeneratedAtUtc.ToString("R")); - if (this.AcceptsOpenMetrics(context.Request.Headers)) + if (openMetricsRequested) { context.Response.ContentType = "application/openmetrics-text; version=1.0.0; charset=utf-8"; } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs index 70b886b520..471addf374 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs @@ -65,7 +65,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(); + var response = await exporter.CollectionManager.EnterCollect(false); try { return new Response @@ -98,7 +98,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon counter.Add(100); // This should use the cache and ignore the second counter update. - var task = exporter.CollectionManager.EnterCollect(); + var task = exporter.CollectionManager.EnterCollect(false); Assert.True(task.IsCompleted); var response = await task; try @@ -129,7 +129,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(); + var response = await exporter.CollectionManager.EnterCollect(false); try { return new Response diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 23990dc9cb..032aaf0dba 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -601,6 +601,6 @@ public void HistogramOneDimensionWithScopeInfo() private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool useOpenMetrics = false) { - return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics); + return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics, useOpenMetrics); } } From 5b5f0df177e3571a9710f01028fb4c876ecc2a28 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Fri, 1 Dec 2023 09:06:53 +1100 Subject: [PATCH 10/23] Add tests --- .../PrometheusCollectionManagerTests.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs index 471addf374..7d8347cc47 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs @@ -24,11 +24,13 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests; public sealed class PrometheusCollectionManagerTests { [Theory] - [InlineData(0)] // disable cache, default value for HttpListener + [InlineData(0, true)] // disable cache, default value for HttpListener + [InlineData(0, false)] // disable cache, default value for HttpListener #if PROMETHEUS_ASPNETCORE - [InlineData(300)] // default value for AspNetCore, no possibility to set on HttpListener + [InlineData(300, true)] // default value for AspNetCore, no possibility to set on HttpListener + [InlineData(300, false)] // default value for AspNetCore, no possibility to set on HttpListener #endif - public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMilliseconds) + public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMilliseconds, bool requestOpenMetrics) { bool cacheEnabled = scrapeResponseCacheDurationMilliseconds != 0; using var meter = new Meter(Utils.GetCurrentMethodName()); @@ -65,7 +67,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(false); + var response = await exporter.CollectionManager.EnterCollect(requestOpenMetrics); try { return new Response @@ -98,7 +100,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon counter.Add(100); // This should use the cache and ignore the second counter update. - var task = exporter.CollectionManager.EnterCollect(false); + var task = exporter.CollectionManager.EnterCollect(requestOpenMetrics); Assert.True(task.IsCompleted); var response = await task; try @@ -129,7 +131,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(false); + var response = await exporter.CollectionManager.EnterCollect(requestOpenMetrics); try { return new Response From b5c80dbcc685131dd660b61884831c8d62ec616c Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 7 Dec 2023 19:50:16 +1100 Subject: [PATCH 11/23] Fix merge --- .../.publicApi/PublicAPI.Unshipped.txt | 2 -- .../PrometheusAspNetCoreOptions.cs | 9 ------- .../PrometheusExporterMiddleware.cs | 2 -- .../.publicApi/PublicAPI.Unshipped.txt | 2 -- .../Internal/PrometheusCollectionManager.cs | 13 +++------ .../Internal/PrometheusExporter.cs | 3 --- .../Internal/PrometheusExporterOptions.cs | 5 ---- .../Internal/PrometheusSerializer.cs | 6 ++--- .../Internal/PrometheusSerializerExt.cs | 8 +++--- .../PrometheusHttpListener.cs | 27 ------------------- ...pListenerMeterProviderBuilderExtensions.cs | 6 +---- .../PrometheusSerializerTests.cs | 1 + 12 files changed, 12 insertions(+), 72 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt index 50e26513d4..0dd120b12d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt @@ -2,8 +2,6 @@ Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.PrometheusAspNetCoreOptions() -> void -OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.OpenMetricsEnabled.get -> bool -OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.OpenMetricsEnabled.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.get -> string OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeResponseCacheDurationMilliseconds.get -> int diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs index ab47be04db..aead3765af 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs @@ -42,14 +42,5 @@ public int ScrapeResponseCacheDurationMilliseconds set => this.ExporterOptions.ScrapeResponseCacheDurationMilliseconds = value; } - /// - /// Gets or sets a value indicating whether to export OpenMetrics compatible scrape responses. Default value: true. - /// - public bool OpenMetricsEnabled - { - get => this.ExporterOptions.OpenMetricsEnabled; - set => this.ExporterOptions.OpenMetricsEnabled = value; - } - internal PrometheusExporterOptions ExporterOptions { get; } = new(); } diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index 5499018bba..72145bdaed 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -28,8 +28,6 @@ namespace OpenTelemetry.Exporter; /// internal sealed class PrometheusExporterMiddleware { - private const string OpenMetricsMediaType = "application/openmetrics-text"; - private readonly PrometheusExporter exporter; /// diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt index 8dbcc600a0..9bc2e72461 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt @@ -1,6 +1,4 @@ OpenTelemetry.Exporter.PrometheusHttpListenerOptions -OpenTelemetry.Exporter.PrometheusHttpListenerOptions.OpenMetricsEnabled.get -> bool -OpenTelemetry.Exporter.PrometheusHttpListenerOptions.OpenMetricsEnabled.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 480f6098ee..a00b0fd767 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -176,7 +176,6 @@ private bool ExecuteCollect(bool openMetricsRequested) this.exporter.OpenMetricsRequested = openMetricsRequested; var result = this.exporter.Collect(Timeout.Infinite); this.exporter.OnExport = null; - this.exporter.OpenMetricsRequested = null; return result; } @@ -186,7 +185,7 @@ private ExportResult OnCollect(Batch metrics) try { - if (this.exporter.OpenMetricsEnabled) + if (this.exporter.OpenMetricsRequested) { this.scopes.Clear(); @@ -202,12 +201,7 @@ private ExportResult OnCollect(Batch metrics) { try { - cursor = PrometheusSerializer.WriteMetric( - this.buffer, - cursor, - metric, - this.GetPrometheusMetric(metric), - this.exporter.OpenMetricsRequested); + cursor = PrometheusSerializer.WriteScopeInfo(this.buffer, cursor, scope); break; } @@ -243,8 +237,7 @@ private ExportResult OnCollect(Batch metrics) cursor, metric, this.GetPrometheusMetric(metric), - this.exporter.OpenMetricsEnabled, - this.exporter.OpenMetricsRequested ?? false); + this.exporter.OpenMetricsRequested); break; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index d09e04fdb6..b02a3a64b6 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -38,7 +38,6 @@ public PrometheusExporter(PrometheusExporterOptions options) Guard.ThrowIfNull(options); this.ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds; - this.OpenMetricsEnabled = options.OpenMetricsEnabled; this.CollectionManager = new PrometheusCollectionManager(this); } @@ -58,8 +57,6 @@ internal Func, ExportResult> OnExport set => this.funcExport = value; } - internal bool? OpenMetricsRequested { get; set; } - internal Action OnDispose { get; set; } internal PrometheusCollectionManager CollectionManager { get; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs index 2e2da9c7d1..2d9a679124 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs @@ -41,9 +41,4 @@ public int ScrapeResponseCacheDurationMilliseconds this.scrapeResponseCacheDurationMilliseconds = value; } } - - /// - /// Gets or sets a value indicating whether to export OpenMetrics compatible scrape responses. Default value: true. - /// - public bool OpenMetricsEnabled { get; set; } = true; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index 4ef1506cdb..cc7ac32a58 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -379,16 +379,16 @@ public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool use } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool openMetricsEnabled, bool writeEnclosingBraces = true) + public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool openMetricsRequested, bool writeEnclosingBraces = true) { - if (tags.Count > 0 || openMetricsEnabled) + if (tags.Count > 0 || openMetricsRequested) { if (writeEnclosingBraces) { buffer[cursor++] = unchecked((byte)'{'); } - if (openMetricsEnabled) + if (openMetricsRequested) { cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); buffer[cursor++] = unchecked((byte)','); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 8739bb481b..340a85e728 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -50,7 +50,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Counter and Gauge cursor = WriteMetricName(buffer, cursor, prometheusMetric); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsEnabled); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsRequested); buffer[cursor++] = unchecked((byte)' '); @@ -101,7 +101,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); - cursor = WriteTags(buffer, cursor, metric, tags, openMetricsEnabled, writeEnclosingBraces: false); + cursor = WriteTags(buffer, cursor, metric, tags, openMetricsRequested, writeEnclosingBraces: false); cursor = WriteAsciiStringNoEscape(buffer, cursor, "le=\""); @@ -127,7 +127,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Histogram sum cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsEnabled); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsRequested); buffer[cursor++] = unchecked((byte)' '); @@ -141,7 +141,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Histogram count cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsEnabled); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsRequested); buffer[cursor++] = unchecked((byte)' '); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index 151fbf9ac8..914f448df9 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -14,7 +14,6 @@ // limitations under the License. // -using System.Collections.Specialized; using System.Net; using OpenTelemetry.Exporter.Prometheus; using OpenTelemetry.Internal; @@ -23,8 +22,6 @@ namespace OpenTelemetry.Exporter; internal sealed class PrometheusHttpListener : IDisposable { - private const string OpenMetricsMediaType = "application/openmetrics-text"; - private readonly PrometheusExporter exporter; private readonly HttpListener httpListener = new(); private readonly object syncObject = new(); @@ -206,28 +203,4 @@ private async Task ProcessRequestAsync(HttpListenerContext context) { } } - - private bool AcceptsOpenMetrics(NameValueCollection headers) - { - var requestAccept = headers["Accept"]; - - if (string.IsNullOrEmpty(requestAccept)) - { - return false; - } - - var acceptTypes = requestAccept.Split(','); - - foreach (var acceptType in acceptTypes) - { - var acceptSubType = acceptType.Split(';').FirstOrDefault()?.Trim(); - - if (acceptSubType == OpenMetricsMediaType) - { - return true; - } - } - - return false; - } } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs index b7f6ea804b..62c5f386b0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -78,11 +78,7 @@ public static MeterProviderBuilder AddPrometheusHttpListener( private static MetricReader BuildPrometheusHttpListenerMetricReader( PrometheusHttpListenerOptions options) { - var exporter = new PrometheusExporter(new PrometheusExporterOptions - { - ScrapeResponseCacheDurationMilliseconds = 0, - OpenMetricsEnabled = options.OpenMetricsEnabled, - }); + var exporter = new PrometheusExporter(new PrometheusExporterOptions { ScrapeResponseCacheDurationMilliseconds = 0 }); var reader = new BaseExportingMetricReader(exporter) { diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 6ba18800c3..2fd3bfceb2 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -528,6 +528,7 @@ public void ScopeInfo() Encoding.UTF8.GetString(buffer, 0, cursor)); } + [Fact] public void SumWithOpenMetricsFormat() { var buffer = new byte[85000]; From f0b263dfbb8f39206fac21fa9ff47b13e27e7aa3 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 7 Dec 2023 20:09:54 +1100 Subject: [PATCH 12/23] Tidy --- .../Internal/PrometheusSerializerExt.cs | 1 - .../PrometheusExporterMiddlewareTests.cs | 1 - .../PrometheusCollectionManagerTests.cs | 2 -- .../PrometheusHttpListenerTests.cs | 1 - 4 files changed, 5 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 340a85e728..306b116756 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,6 @@ public static bool CanWriteMetric(Metric metric) return true; } - public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested = false) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index 8533ed78a1..072b3b4511 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -17,7 +17,6 @@ #if !NETFRAMEWORK using System.Diagnostics.Metrics; using System.Net; -using System.Net.Http.Headers; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs index 27e621083a..2611126379 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs @@ -68,7 +68,6 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon collectTasks[i] = Task.Run(async () => { var response = await exporter.CollectionManager.EnterCollect(openMetricsRequested); - try { return new Response @@ -133,7 +132,6 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon collectTasks[i] = Task.Run(async () => { var response = await exporter.CollectionManager.EnterCollect(openMetricsRequested); - try { return new Response diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index 333aff4648..e9e270dd5b 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -16,7 +16,6 @@ using System.Diagnostics.Metrics; using System.Net; -using System.Net.Http.Headers; #if NETFRAMEWORK using System.Net.Http; #endif From 6753ad9d82fa18f602c05707965ed880b2ebdeea Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 7 Dec 2023 20:11:26 +1100 Subject: [PATCH 13/23] Fixed changelog --- src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md | 2 +- .../CHANGELOG.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index 1d2f0776b0..fd4774918f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) -* Added ability to export otel_scop_info and handle OpenMetrics requests from Prometheus ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* Added ability to export otel_scop_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) ## 1.7.0-rc.1 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index 16185be88d..f7e8ec14bb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -3,7 +3,8 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) - +* Added ability to export otel_scop_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* ## 1.7.0-rc.1 Released 2023-Nov-29 From d452ab2f50d50d3926e735d976caddaa7f79da1c Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 7 Dec 2023 22:04:19 +1100 Subject: [PATCH 14/23] Add tests and ability to disable scope info --- examples/AspNetCore/Instrumentation.cs | 2 +- .../.publicApi/PublicAPI.Unshipped.txt | 2 + .../PrometheusAspNetCoreOptions.cs | 9 ++ .../.publicApi/PublicAPI.Unshipped.txt | 2 + .../Internal/PrometheusCollectionManager.cs | 5 +- .../Internal/PrometheusExporter.cs | 3 + .../Internal/PrometheusExporterOptions.cs | 5 + .../Internal/PrometheusSerializer.cs | 6 +- .../Internal/PrometheusSerializerExt.cs | 10 +- ...pListenerMeterProviderBuilderExtensions.cs | 6 +- .../PrometheusHttpListenerOptions.cs | 4 +- .../PrometheusExporterMiddlewareTests.cs | 67 +++++++++-- .../PrometheusHttpListenerTests.cs | 66 +++++++++-- .../PrometheusSerializerTests.cs | 105 ++++++++++++++---- 14 files changed, 237 insertions(+), 55 deletions(-) diff --git a/examples/AspNetCore/Instrumentation.cs b/examples/AspNetCore/Instrumentation.cs index e4583887e0..62d93dbcae 100644 --- a/examples/AspNetCore/Instrumentation.cs +++ b/examples/AspNetCore/Instrumentation.cs @@ -35,7 +35,7 @@ public Instrumentation() string? version = typeof(Instrumentation).Assembly.GetName().Version?.ToString(); this.ActivitySource = new ActivitySource(ActivitySourceName, version); this.meter = new Meter(MeterName, version); - this.FreezingDaysCounter = this.meter.CreateCounter("weather.days.freezing", "The number of days where the temperature is below freezing"); + this.FreezingDaysCounter = this.meter.CreateCounter("weather.days.freezing", description: "The number of days where the temperature is below freezing"); } public ActivitySource ActivitySource { get; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt index 0dd120b12d..f904582a47 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt @@ -2,6 +2,8 @@ Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.PrometheusAspNetCoreOptions() -> void +OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.get -> bool +OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.get -> string OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeResponseCacheDurationMilliseconds.get -> int diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs index aead3765af..e1ca1636c3 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs @@ -42,5 +42,14 @@ public int ScrapeResponseCacheDurationMilliseconds set => this.ExporterOptions.ScrapeResponseCacheDurationMilliseconds = value; } + /// + /// Gets or sets a value indicating whether to export scope info. Default value: true. + /// + public bool ScopeInfoEnabled + { + get => this.ExporterOptions.ScopeInfoEnabled; + set => this.ExporterOptions.ScopeInfoEnabled = value; + } + internal PrometheusExporterOptions ExporterOptions { get; } = new(); } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt index 9bc2e72461..62cb390376 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ OpenTelemetry.Exporter.PrometheusHttpListenerOptions +OpenTelemetry.Exporter.PrometheusHttpListenerOptions.ScopeInfoEnabled.get -> bool +OpenTelemetry.Exporter.PrometheusHttpListenerOptions.ScopeInfoEnabled.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index a00b0fd767..84509e8376 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -185,7 +185,7 @@ private ExportResult OnCollect(Batch metrics) try { - if (this.exporter.OpenMetricsRequested) + if (this.exporter.ScopeInfoEnabled && this.exporter.OpenMetricsRequested) { this.scopes.Clear(); @@ -237,7 +237,8 @@ private ExportResult OnCollect(Batch metrics) cursor, metric, this.GetPrometheusMetric(metric), - this.exporter.OpenMetricsRequested); + this.exporter.OpenMetricsRequested, + this.exporter.ScopeInfoEnabled); break; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index b02a3a64b6..2e8de84283 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -38,6 +38,7 @@ public PrometheusExporter(PrometheusExporterOptions options) Guard.ThrowIfNull(options); this.ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds; + this.ScopeInfoEnabled = options.ScopeInfoEnabled; this.CollectionManager = new PrometheusCollectionManager(this); } @@ -65,6 +66,8 @@ internal Func, ExportResult> OnExport internal bool OpenMetricsRequested { get; set; } + internal bool ScopeInfoEnabled { get; set; } + /// public override ExportResult Export(in Batch metrics) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs index 2d9a679124..af1e2f6f09 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs @@ -41,4 +41,9 @@ public int ScrapeResponseCacheDurationMilliseconds this.scrapeResponseCacheDurationMilliseconds = value; } } + + /// + /// Gets or sets a value indicating whether to export scope info. Default value: true. + /// + public bool ScopeInfoEnabled { get; set; } = true; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index cc7ac32a58..e3cee944bf 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -379,16 +379,16 @@ public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool use } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool openMetricsRequested, bool writeEnclosingBraces = true) + public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool scopeInfoEnabled, bool writeEnclosingBraces = true) { - if (tags.Count > 0 || openMetricsRequested) + if (tags.Count > 0 || scopeInfoEnabled) { if (writeEnclosingBraces) { buffer[cursor++] = unchecked((byte)'{'); } - if (openMetricsRequested) + if (scopeInfoEnabled) { cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); buffer[cursor++] = unchecked((byte)','); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 306b116756..76396fd287 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,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 = false, bool scopeInfoEnabled = true) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); @@ -49,7 +49,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Counter and Gauge cursor = WriteMetricName(buffer, cursor, prometheusMetric); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsRequested); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, scopeInfoEnabled); buffer[cursor++] = unchecked((byte)' '); @@ -100,7 +100,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); - cursor = WriteTags(buffer, cursor, metric, tags, openMetricsRequested, writeEnclosingBraces: false); + cursor = WriteTags(buffer, cursor, metric, tags, scopeInfoEnabled, writeEnclosingBraces: false); cursor = WriteAsciiStringNoEscape(buffer, cursor, "le=\""); @@ -126,7 +126,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Histogram sum cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsRequested); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, scopeInfoEnabled); buffer[cursor++] = unchecked((byte)' '); @@ -140,7 +140,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Histogram count cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, openMetricsRequested); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, scopeInfoEnabled); buffer[cursor++] = unchecked((byte)' '); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs index 62c5f386b0..20b1eabcb9 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -78,7 +78,11 @@ public static MeterProviderBuilder AddPrometheusHttpListener( private static MetricReader BuildPrometheusHttpListenerMetricReader( PrometheusHttpListenerOptions options) { - var exporter = new PrometheusExporter(new PrometheusExporterOptions { ScrapeResponseCacheDurationMilliseconds = 0 }); + var exporter = new PrometheusExporter(new PrometheusExporterOptions + { + ScrapeResponseCacheDurationMilliseconds = 0, + ScopeInfoEnabled = options.ScopeInfoEnabled, + }); var reader = new BaseExportingMetricReader(exporter) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs index ebc265de5f..7f926f572f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs @@ -31,9 +31,9 @@ public class PrometheusHttpListenerOptions public string ScrapeEndpointPath { get; set; } = "/metrics"; /// - /// Gets or sets a value indicating whether to export OpenMetrics compatible scrape responses. Default value: true. + /// Gets or sets a value indicating whether to export scope info. Default value: true. /// - public bool OpenMetricsEnabled { get; set; } = true; + public bool ScopeInfoEnabled { get; set; } = true; /// /// Gets or sets the URI (Uniform Resource Identifier) prefixes to use for the http listener. diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index 072b3b4511..956b16079a 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -32,6 +32,8 @@ namespace OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests; public sealed class PrometheusExporterMiddlewareTests { + private const string MeterVersion = "1.0.1"; + private static readonly string MeterName = Utils.GetCurrentMethodName(); [Fact] @@ -247,6 +249,16 @@ public Task PrometheusExporterMiddlewareIntegration_TextPlainResponse() acceptHeader: "text/plain"); } + [Fact] + public Task PrometheusExporterMiddlewareIntegration_TextPlainResponse_NoScopeInfo() + { + return RunPrometheusExporterMiddlewareIntegrationTest( + "/metrics", + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + acceptHeader: "text/plain", + scopeInfoEnabled: false); + } + [Fact] public Task PrometheusExporterMiddlewareIntegration_UseOpenMetricsVersionHeader() { @@ -256,6 +268,15 @@ public Task PrometheusExporterMiddlewareIntegration_UseOpenMetricsVersionHeader( acceptHeader: "application/openmetrics-text; version=1.0.0"); } + [Fact] + public Task PrometheusExporterMiddlewareIntegration_NoScopeInfo() + { + return RunPrometheusExporterMiddlewareIntegrationTest( + "/metrics", + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + scopeInfoEnabled: false); + } + private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string path, Action configure, @@ -264,7 +285,8 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( bool registerMeterProvider = true, Action configureOptions = null, bool skipMetrics = false, - string acceptHeader = "application/openmetrics-text") + string acceptHeader = "application/openmetrics-text", + bool scopeInfoEnabled = true) { var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); @@ -279,6 +301,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( .AddMeter(MeterName) .AddPrometheusExporter(o => { + o.ScopeInfoEnabled = scopeInfoEnabled; configureOptions?.Invoke(o); })); } @@ -294,7 +317,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( new KeyValuePair("key2", "value2"), }; - using var meter = new Meter(MeterName); + using var meter = new Meter(MeterName, MeterVersion); var beginTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -331,14 +354,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( } string content = await response.Content.ReadAsStringAsync(); - - string expected = requestOpenMetrics - ? "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" - + "# EOF\n" - : "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" - + "# EOF\n"; + string expected = GetExpectedContent(requestOpenMetrics, scopeInfoEnabled); var matches = Regex.Matches(content, ("^" + expected + "$").Replace('\'', '"')); @@ -357,5 +373,36 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( await host.StopAsync(); } + + private static string GetExpectedContent(bool requestedOpenMetrics, bool scopeInfoEnabled) + { + if (requestedOpenMetrics && scopeInfoEnabled) + { + return "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + + "# EOF\n"; + } + + if (!requestedOpenMetrics && scopeInfoEnabled) + { + return "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + + "# EOF\n"; + } + + if (requestedOpenMetrics && !scopeInfoEnabled) + { + return "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" + + "# EOF\n"; + } + + return "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" + + "# EOF\n"; + } } #endif diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index e9e270dd5b..c1e453b125 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -27,7 +27,9 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests; public class PrometheusHttpListenerTests { - private readonly string meterName = Utils.GetCurrentMethodName(); + private const string MeterVersion = "1.0.1"; + + private static readonly string MeterName = Utils.GetCurrentMethodName(); [Theory] [InlineData("http://+:9464")] @@ -96,13 +98,25 @@ public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics() await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty); } + [Fact] + public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics_NoScopeInfo() + { + await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, scopeInfoEnabled: false); + } + [Fact] public async Task PrometheusExporterHttpServerIntegration_UseOpenMetricsVersionHeader() { await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); } - private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text") + [Fact] + public async Task PrometheusExporterHttpServerIntegration_NoScopeInfo() + { + await this.RunPrometheusExporterHttpServerIntegrationTest(scopeInfoEnabled: false); + } + + private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", bool scopeInfoEnabled = true) { var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); @@ -112,7 +126,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri string address = null; MeterProvider provider = null; - using var meter = new Meter(this.meterName); + using var meter = new Meter(MeterName, MeterVersion); while (retryAttempts-- != 0) { @@ -123,7 +137,11 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri { provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { address }) + .AddPrometheusHttpListener(options => + { + options.ScopeInfoEnabled = scopeInfoEnabled; + options.UriPrefixes = new string[] { address }; + }) .Build(); break; @@ -176,14 +194,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri } var content = await response.Content.ReadAsStringAsync(); - - var expected = requestOpenMetrics - ? "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 \\d+\\.\\d{3}\n" - + "# EOF\n" - : "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 \\d+\n" - + "# EOF\n"; + var expected = GetExpectedContent(requestOpenMetrics, scopeInfoEnabled); Assert.Matches(("^" + expected + "$").Replace('\'', '"'), content); } @@ -194,4 +205,35 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri provider.Dispose(); } + + private static string GetExpectedContent(bool requestedOpenMetrics, bool scopeInfoEnabled) + { + if (requestedOpenMetrics && scopeInfoEnabled) + { + return "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + + "# EOF\n"; + } + + if (!requestedOpenMetrics && scopeInfoEnabled) + { + return "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + + "# EOF\n"; + } + + if (requestedOpenMetrics && !scopeInfoEnabled) + { + return "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" + + "# EOF\n"; + } + + return "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" + + "# EOF\n"; + } } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 2fd3bfceb2..9c8f8ecc2d 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -512,22 +512,6 @@ public void ExponentialHistogramIsIgnoredForNow() Assert.False(PrometheusSerializer.CanWriteMetric(metrics[0])); } - [Fact] - public void ScopeInfo() - { - var buffer = new byte[85000]; - - var cursor = PrometheusSerializer.WriteScopeInfo(buffer, 0, "test_meter"); - - Assert.Matches( - ("^" - + "# TYPE otel_scope_info info\n" - + "# HELP otel_scope_info Scope metadata\n" - + "otel_scope_info{otel_scope_name='test_meter'} 1\n" - + "$").Replace('\'', '"'), - Encoding.UTF8.GetString(buffer, 0, cursor)); - } - [Fact] public void SumWithOpenMetricsFormat() { @@ -556,7 +540,7 @@ public void SumWithOpenMetricsFormat() } [Fact] - public void HistogramOneDimensionWithScopeInfo() + public void HistogramOneDimensionWithOpenMetricsFormat() { var buffer = new byte[85000]; var metrics = new List(); @@ -599,8 +583,91 @@ public void HistogramOneDimensionWithScopeInfo() Encoding.UTF8.GetString(buffer, 0, cursor)); } - private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool useOpenMetrics = false) + [Fact] + public void ScopeInfo() + { + var buffer = new byte[85000]; + + var cursor = PrometheusSerializer.WriteScopeInfo(buffer, 0, "test_meter"); + + Assert.Matches( + ("^" + + "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + "otel_scope_info{otel_scope_name='test_meter'} 1\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void SumWithOpenScopeInfo() + { + var buffer = new byte[85000]; + var metrics = new List(); + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + var counter = meter.CreateUpDownCounter("test_updown_counter"); + counter.Add(10); + counter.Add(-11); + provider.ForceFlush(); + var cursor = WriteMetric(buffer, 0, metrics[0], true, true); + Assert.Matches( + ("^" + + "# TYPE test_updown_counter gauge\n" + + $"test_updown_counter{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} -1 \\d+\\.\\d{{3}}\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + [Fact] + public void HistogramOneDimensionWithScopeInfo() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName(), "1.0.0"); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18, new KeyValuePair("x", "1")); + histogram.Record(100, new KeyValuePair("x", "1")); + + provider.ForceFlush(); + + var cursor = WriteMetric(buffer, 0, metrics[0], true, true); + Assert.Matches( + ("^" + + "# TYPE test_histogram histogram\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='0'}} 0 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='5'}} 0 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='10'}} 0 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='25'}} 1 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='50'}} 1 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='75'}} 1 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='100'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='250'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='500'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='750'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='1000'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='2500'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='5000'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='7500'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='10000'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1',le='\\+Inf'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_sum{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1'}} 118 \\d+\\.\\d{{3}}\n" + + $"test_histogram_count{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0',x='1'}} 2 \\d+\\.\\d{{3}}\n" + + "$").Replace('\'', '"'), + Encoding.UTF8.GetString(buffer, 0, cursor)); + } + + private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool useOpenMetrics = false, bool scopeInfoEnabled = false) { - return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics); + return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics, scopeInfoEnabled); } } From 8727a79acd6c868a8894fb1db6d9b34847c359bf Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 7 Dec 2023 22:10:25 +1100 Subject: [PATCH 15/23] Fix build --- .../CHANGELOG.md | 2 +- .../PrometheusHttpListenerTests.cs | 62 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index f7e8ec14bb..364c5c0e72 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -4,7 +4,7 @@ * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) * Added ability to export otel_scop_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) -* + ## 1.7.0-rc.1 Released 2023-Nov-29 diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index c1e453b125..f590c575d6 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -116,6 +116,37 @@ public async Task PrometheusExporterHttpServerIntegration_NoScopeInfo() await this.RunPrometheusExporterHttpServerIntegrationTest(scopeInfoEnabled: false); } + private static string GetExpectedContent(bool requestedOpenMetrics, bool scopeInfoEnabled) + { + if (requestedOpenMetrics && scopeInfoEnabled) + { + return "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + + "# EOF\n"; + } + + if (!requestedOpenMetrics && scopeInfoEnabled) + { + return "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + + "# EOF\n"; + } + + if (requestedOpenMetrics && !scopeInfoEnabled) + { + return "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" + + "# EOF\n"; + } + + return "# TYPE counter_double_total counter\n" + + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" + + "# EOF\n"; + } + private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", bool scopeInfoEnabled = true) { var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); @@ -205,35 +236,4 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri provider.Dispose(); } - - private static string GetExpectedContent(bool requestedOpenMetrics, bool scopeInfoEnabled) - { - if (requestedOpenMetrics && scopeInfoEnabled) - { - return "# TYPE otel_scope_info info\n" - + "# HELP otel_scope_info Scope metadata\n" - + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" - + "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" - + "# EOF\n"; - } - - if (!requestedOpenMetrics && scopeInfoEnabled) - { - return "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" - + "# EOF\n"; - } - - if (requestedOpenMetrics && !scopeInfoEnabled) - { - return "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" - + "# EOF\n"; - } - - return "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" - + "# EOF\n"; - } } From 63f0b3629ee9db012f880b71db7211446d250243 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Thu, 7 Dec 2023 22:32:36 +1100 Subject: [PATCH 16/23] Spelling --- src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md | 2 +- src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index fd4774918f..22fbe7a4ce 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) -* Added ability to export otel_scop_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* Added ability to export otel_scope_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) ## 1.7.0-rc.1 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index 364c5c0e72..55b7ca7fa8 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) -* Added ability to export otel_scop_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* Added ability to export otel_scope_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) ## 1.7.0-rc.1 From 033a9036179f170d10f26ae1b65e6d5c131a5698 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Sun, 10 Dec 2023 19:19:57 +1100 Subject: [PATCH 17/23] Scope info is not optional --- .../.publicApi/PublicAPI.Unshipped.txt | 2 - .../PrometheusAspNetCoreOptions.cs | 9 --- .../.publicApi/PublicAPI.Unshipped.txt | 2 - .../Internal/PrometheusCollectionManager.cs | 5 +- .../Internal/PrometheusExporter.cs | 3 - .../Internal/PrometheusExporterOptions.cs | 5 -- .../Internal/PrometheusSerializer.cs | 47 ++++++------- .../Internal/PrometheusSerializerExt.cs | 10 +-- ...pListenerMeterProviderBuilderExtensions.cs | 6 +- .../PrometheusHttpListenerOptions.cs | 5 -- .../PrometheusExporterMiddlewareTests.cs | 66 ++++--------------- .../PrometheusHttpListenerTests.cs | 58 ++++------------ .../PrometheusSerializerTests.cs | 8 +-- 13 files changed, 57 insertions(+), 169 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt index f904582a47..0dd120b12d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/.publicApi/PublicAPI.Unshipped.txt @@ -2,8 +2,6 @@ Microsoft.AspNetCore.Builder.PrometheusExporterApplicationBuilderExtensions Microsoft.AspNetCore.Builder.PrometheusExporterEndpointRouteBuilderExtensions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.PrometheusAspNetCoreOptions() -> void -OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.get -> bool -OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScopeInfoEnabled.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.get -> string OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeEndpointPath.set -> void OpenTelemetry.Exporter.PrometheusAspNetCoreOptions.ScrapeResponseCacheDurationMilliseconds.get -> int diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs index e1ca1636c3..aead3765af 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusAspNetCoreOptions.cs @@ -42,14 +42,5 @@ public int ScrapeResponseCacheDurationMilliseconds set => this.ExporterOptions.ScrapeResponseCacheDurationMilliseconds = value; } - /// - /// Gets or sets a value indicating whether to export scope info. Default value: true. - /// - public bool ScopeInfoEnabled - { - get => this.ExporterOptions.ScopeInfoEnabled; - set => this.ExporterOptions.ScopeInfoEnabled = value; - } - internal PrometheusExporterOptions ExporterOptions { get; } = new(); } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt index 62cb390376..9bc2e72461 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/.publicApi/PublicAPI.Unshipped.txt @@ -1,6 +1,4 @@ OpenTelemetry.Exporter.PrometheusHttpListenerOptions -OpenTelemetry.Exporter.PrometheusHttpListenerOptions.ScopeInfoEnabled.get -> bool -OpenTelemetry.Exporter.PrometheusHttpListenerOptions.ScopeInfoEnabled.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.get -> System.Collections.Generic.IReadOnlyCollection OpenTelemetry.Exporter.PrometheusHttpListenerOptions.UriPrefixes.set -> void OpenTelemetry.Exporter.PrometheusHttpListenerOptions.PrometheusHttpListenerOptions() -> void diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 84509e8376..a00b0fd767 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -185,7 +185,7 @@ private ExportResult OnCollect(Batch metrics) try { - if (this.exporter.ScopeInfoEnabled && this.exporter.OpenMetricsRequested) + if (this.exporter.OpenMetricsRequested) { this.scopes.Clear(); @@ -237,8 +237,7 @@ private ExportResult OnCollect(Batch metrics) cursor, metric, this.GetPrometheusMetric(metric), - this.exporter.OpenMetricsRequested, - this.exporter.ScopeInfoEnabled); + this.exporter.OpenMetricsRequested); break; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs index 2e8de84283..b02a3a64b6 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporter.cs @@ -38,7 +38,6 @@ public PrometheusExporter(PrometheusExporterOptions options) Guard.ThrowIfNull(options); this.ScrapeResponseCacheDurationMilliseconds = options.ScrapeResponseCacheDurationMilliseconds; - this.ScopeInfoEnabled = options.ScopeInfoEnabled; this.CollectionManager = new PrometheusCollectionManager(this); } @@ -66,8 +65,6 @@ internal Func, ExportResult> OnExport internal bool OpenMetricsRequested { get; set; } - internal bool ScopeInfoEnabled { get; set; } - /// public override ExportResult Export(in Batch metrics) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs index af1e2f6f09..2d9a679124 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusExporterOptions.cs @@ -41,9 +41,4 @@ public int ScrapeResponseCacheDurationMilliseconds this.scrapeResponseCacheDurationMilliseconds = value; } } - - /// - /// Gets or sets a value indicating whether to export scope info. Default value: true. - /// - public bool ScopeInfoEnabled { get; set; } = true; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index e3cee944bf..e664e32edb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -379,37 +379,32 @@ public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool use } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool scopeInfoEnabled, bool writeEnclosingBraces = true) + public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool writeEnclosingBraces = true) { - if (tags.Count > 0 || scopeInfoEnabled) + if (writeEnclosingBraces) { - if (writeEnclosingBraces) - { - buffer[cursor++] = unchecked((byte)'{'); - } + buffer[cursor++] = unchecked((byte)'{'); + } - if (scopeInfoEnabled) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); - buffer[cursor++] = unchecked((byte)','); - - if (!string.IsNullOrEmpty(metric.MeterVersion)) - { - cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); - buffer[cursor++] = unchecked((byte)','); - } - } + cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName); + buffer[cursor++] = unchecked((byte)','); - foreach (var tag in tags) - { - cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); - buffer[cursor++] = unchecked((byte)','); - } + if (!string.IsNullOrEmpty(metric.MeterVersion)) + { + cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion); + buffer[cursor++] = unchecked((byte)','); + } - if (writeEnclosingBraces) - { - buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. - } + foreach (var tag in tags) + { + cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value); + buffer[cursor++] = unchecked((byte)','); + } + + if (writeEnclosingBraces) + { + buffer[cursor - 1] = + unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. } return cursor; diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 76396fd287..0dce96b335 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -35,7 +35,7 @@ public static bool CanWriteMetric(Metric metric) return true; } - public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested = false, bool scopeInfoEnabled = true) + public static int WriteMetric(byte[] buffer, int cursor, Metric metric, PrometheusMetric prometheusMetric, bool openMetricsRequested = false) { cursor = WriteTypeMetadata(buffer, cursor, prometheusMetric); cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric); @@ -49,7 +49,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Counter and Gauge cursor = WriteMetricName(buffer, cursor, prometheusMetric); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, scopeInfoEnabled); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags); buffer[cursor++] = unchecked((byte)' '); @@ -100,7 +100,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{"); - cursor = WriteTags(buffer, cursor, metric, tags, scopeInfoEnabled, writeEnclosingBraces: false); + cursor = WriteTags(buffer, cursor, metric, tags, writeEnclosingBraces: false); cursor = WriteAsciiStringNoEscape(buffer, cursor, "le=\""); @@ -126,7 +126,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Histogram sum cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum"); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, scopeInfoEnabled); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags); buffer[cursor++] = unchecked((byte)' '); @@ -140,7 +140,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe // Histogram count cursor = WriteMetricName(buffer, cursor, prometheusMetric); cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count"); - cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags, scopeInfoEnabled); + cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags); buffer[cursor++] = unchecked((byte)' '); diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs index 20b1eabcb9..62c5f386b0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -78,11 +78,7 @@ public static MeterProviderBuilder AddPrometheusHttpListener( private static MetricReader BuildPrometheusHttpListenerMetricReader( PrometheusHttpListenerOptions options) { - var exporter = new PrometheusExporter(new PrometheusExporterOptions - { - ScrapeResponseCacheDurationMilliseconds = 0, - ScopeInfoEnabled = options.ScopeInfoEnabled, - }); + var exporter = new PrometheusExporter(new PrometheusExporterOptions { ScrapeResponseCacheDurationMilliseconds = 0 }); var reader = new BaseExportingMetricReader(exporter) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs index 7f926f572f..51954fe15d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerOptions.cs @@ -30,11 +30,6 @@ public class PrometheusHttpListenerOptions /// public string ScrapeEndpointPath { get; set; } = "/metrics"; - /// - /// Gets or sets a value indicating whether to export scope info. Default value: true. - /// - public bool ScopeInfoEnabled { get; set; } = true; - /// /// Gets or sets the URI (Uniform Resource Identifier) prefixes to use for the http listener. /// Default value: ["http://localhost:9464/"]. diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index 956b16079a..6d4098bf94 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -249,16 +249,6 @@ public Task PrometheusExporterMiddlewareIntegration_TextPlainResponse() acceptHeader: "text/plain"); } - [Fact] - public Task PrometheusExporterMiddlewareIntegration_TextPlainResponse_NoScopeInfo() - { - return RunPrometheusExporterMiddlewareIntegrationTest( - "/metrics", - app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), - acceptHeader: "text/plain", - scopeInfoEnabled: false); - } - [Fact] public Task PrometheusExporterMiddlewareIntegration_UseOpenMetricsVersionHeader() { @@ -268,15 +258,6 @@ public Task PrometheusExporterMiddlewareIntegration_UseOpenMetricsVersionHeader( acceptHeader: "application/openmetrics-text; version=1.0.0"); } - [Fact] - public Task PrometheusExporterMiddlewareIntegration_NoScopeInfo() - { - return RunPrometheusExporterMiddlewareIntegrationTest( - "/metrics", - app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), - scopeInfoEnabled: false); - } - private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string path, Action configure, @@ -285,8 +266,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( bool registerMeterProvider = true, Action configureOptions = null, bool skipMetrics = false, - string acceptHeader = "application/openmetrics-text", - bool scopeInfoEnabled = true) + string acceptHeader = "application/openmetrics-text") { var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); @@ -301,7 +281,6 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( .AddMeter(MeterName) .AddPrometheusExporter(o => { - o.ScopeInfoEnabled = scopeInfoEnabled; configureOptions?.Invoke(o); })); } @@ -354,7 +333,17 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( } string content = await response.Content.ReadAsStringAsync(); - string expected = GetExpectedContent(requestOpenMetrics, scopeInfoEnabled); + + string expected = requestOpenMetrics + ? "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + + "# EOF\n" + : "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + + "# EOF\n"; var matches = Regex.Matches(content, ("^" + expected + "$").Replace('\'', '"')); @@ -373,36 +362,5 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( await host.StopAsync(); } - - private static string GetExpectedContent(bool requestedOpenMetrics, bool scopeInfoEnabled) - { - if (requestedOpenMetrics && scopeInfoEnabled) - { - return "# TYPE otel_scope_info info\n" - + "# HELP otel_scope_info Scope metadata\n" - + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" - + "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" - + "# EOF\n"; - } - - if (!requestedOpenMetrics && scopeInfoEnabled) - { - return "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" - + "# EOF\n"; - } - - if (requestedOpenMetrics && !scopeInfoEnabled) - { - return "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" - + "# EOF\n"; - } - - return "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" - + "# EOF\n"; - } } #endif diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index f590c575d6..667d90a93d 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -98,56 +98,13 @@ public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics() await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty); } - [Fact] - public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics_NoScopeInfo() - { - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, scopeInfoEnabled: false); - } - [Fact] public async Task PrometheusExporterHttpServerIntegration_UseOpenMetricsVersionHeader() { await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); } - [Fact] - public async Task PrometheusExporterHttpServerIntegration_NoScopeInfo() - { - await this.RunPrometheusExporterHttpServerIntegrationTest(scopeInfoEnabled: false); - } - - private static string GetExpectedContent(bool requestedOpenMetrics, bool scopeInfoEnabled) - { - if (requestedOpenMetrics && scopeInfoEnabled) - { - return "# TYPE otel_scope_info info\n" - + "# HELP otel_scope_info Scope metadata\n" - + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" - + "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" - + "# EOF\n"; - } - - if (!requestedOpenMetrics && scopeInfoEnabled) - { - return "# TYPE counter_double_total counter\n" - + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" - + "# EOF\n"; - } - - if (requestedOpenMetrics && !scopeInfoEnabled) - { - return "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+\\.\\d{3})\n" - + "# EOF\n"; - } - - return "# TYPE counter_double_total counter\n" - + "counter_double_total{key1='value1',key2='value2'} 101.17 (\\d+)\n" - + "# EOF\n"; - } - - private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", bool scopeInfoEnabled = true) + private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text") { var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); @@ -170,7 +127,6 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri .AddMeter(meter.Name) .AddPrometheusHttpListener(options => { - options.ScopeInfoEnabled = scopeInfoEnabled; options.UriPrefixes = new string[] { address }; }) .Build(); @@ -225,7 +181,17 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri } var content = await response.Content.ReadAsStringAsync(); - var expected = GetExpectedContent(requestOpenMetrics, scopeInfoEnabled); + + var expected = requestOpenMetrics + ? "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + + "# EOF\n" + : "# TYPE counter_double_total counter\n" + + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + + "# EOF\n"; Assert.Matches(("^" + expected + "$").Replace('\'', '"'), content); } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 9c8f8ecc2d..04b622f98c 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -613,7 +613,7 @@ public void SumWithOpenScopeInfo() counter.Add(10); counter.Add(-11); provider.ForceFlush(); - var cursor = WriteMetric(buffer, 0, metrics[0], true, true); + var cursor = WriteMetric(buffer, 0, metrics[0], true); Assert.Matches( ("^" + "# TYPE test_updown_counter gauge\n" @@ -640,7 +640,7 @@ public void HistogramOneDimensionWithScopeInfo() provider.ForceFlush(); - var cursor = WriteMetric(buffer, 0, metrics[0], true, true); + var cursor = WriteMetric(buffer, 0, metrics[0], true); Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" @@ -666,8 +666,8 @@ public void HistogramOneDimensionWithScopeInfo() Encoding.UTF8.GetString(buffer, 0, cursor)); } - private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool useOpenMetrics = false, bool scopeInfoEnabled = false) + private static int WriteMetric(byte[] buffer, int cursor, Metric metric, bool useOpenMetrics = false) { - return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics, scopeInfoEnabled); + return PrometheusSerializer.WriteMetric(buffer, cursor, metric, PrometheusMetric.Create(metric), useOpenMetrics); } } From 89f735db09b92883ccf868f445752ab0dd14ba82 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Sun, 10 Dec 2023 19:23:36 +1100 Subject: [PATCH 18/23] Tidy code --- .../Internal/PrometheusSerializer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index 85ffa915f5..69365d4e0f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -390,8 +390,7 @@ public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTa if (writeEnclosingBraces) { - buffer[cursor - 1] = - unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. + buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. } return cursor; From 50a9bde74fbdd6a51d6955f68bf5576e11e8e046 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Sun, 10 Dec 2023 20:26:13 +1100 Subject: [PATCH 19/23] Fix tests --- .../Internal/PrometheusCollectionManager.cs | 47 ++-- .../PrometheusExporterMiddlewareTests.cs | 5 +- .../PrometheusHttpListenerTests.cs | 5 +- .../PrometheusSerializerTests.cs | 244 +++++++++--------- 4 files changed, 152 insertions(+), 149 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 4417e8dfe2..af1bd9f139 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -172,38 +172,35 @@ private ExportResult OnCollect(Batch metrics) try { - if (this.exporter.OpenMetricsRequested) - { - this.scopes.Clear(); + this.scopes.Clear(); - foreach (var metric in metrics) + foreach (var metric in metrics) + { + if (PrometheusSerializer.CanWriteMetric(metric)) { - if (PrometheusSerializer.CanWriteMetric(metric)) - { - this.scopes.Add(metric.MeterName); - } + this.scopes.Add(metric.MeterName); } + } - foreach (var scope in this.scopes) + foreach (var scope in this.scopes) + { + try { - try - { - cursor = PrometheusSerializer.WriteScopeInfo(this.buffer, cursor, scope); + cursor = PrometheusSerializer.WriteScopeInfo(this.buffer, cursor, scope); - break; - } - catch (IndexOutOfRangeException) + break; + } + catch (IndexOutOfRangeException) + { + if (!this.IncreaseBufferSize()) { - if (!this.IncreaseBufferSize()) - { - // there are two cases we might run into the following condition: - // 1. we have many metrics to be exported - in this case we probably want - // to put some upper limit and allow the user to configure it. - // 2. we got an IndexOutOfRangeException which was triggered by some other - // code instead of the buffer[cursor++] - in this case we should give up - // at certain point rather than allocating like crazy. - throw; - } + // there are two cases we might run into the following condition: + // 1. we have many metrics to be exported - in this case we probably want + // to put some upper limit and allow the user to configure it. + // 2. we got an IndexOutOfRangeException which was triggered by some other + // code instead of the buffer[cursor++] - in this case we should give up + // at certain point rather than allocating like crazy. + throw; } } } diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index 7e1a6c80ba..3b972939f3 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -328,7 +328,10 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( + "# TYPE counter_double_total counter\n" + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + "# EOF\n" - : "# TYPE counter_double_total counter\n" + : "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + "# EOF\n"; diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index 2d0271d84e..5c19c2ec12 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -176,7 +176,10 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri + "# TYPE counter_double_total counter\n" + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+\\.\\d{{3}})\n" + "# EOF\n" - : "# TYPE counter_double_total counter\n" + : "# TYPE otel_scope_info info\n" + + "# HELP otel_scope_info Scope metadata\n" + + $"otel_scope_info{{otel_scope_name='{MeterName}'}} 1\n" + + "# TYPE counter_double_total counter\n" + $"counter_double_total{{otel_scope_name='{MeterName}',otel_scope_version='{MeterVersion}',key1='value1',key2='value2'}} 101.17 (\\d+)\n" + "# EOF\n"; diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 8eedf942be..f5b96d4912 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -31,7 +31,7 @@ public void GaugeZeroDimension() Assert.Matches( ("^" + "# TYPE test_gauge gauge\n" - + "test_gauge 123 \\d+\n" + + $"test_gauge{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 123 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -57,7 +57,7 @@ public void GaugeZeroDimensionWithDescription() ("^" + "# TYPE test_gauge gauge\n" + "# HELP test_gauge Hello, world!\n" - + "test_gauge 123 \\d+\n" + + $"test_gauge{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 123 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -83,7 +83,7 @@ public void GaugeZeroDimensionWithUnit() ("^" + "# TYPE test_gauge_seconds gauge\n" + "# UNIT test_gauge_seconds seconds\n" - + "test_gauge_seconds 123 \\d+\n" + + $"test_gauge_seconds{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 123 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -110,7 +110,7 @@ public void GaugeZeroDimensionWithDescriptionAndUnit() + "# TYPE test_gauge_seconds gauge\n" + "# UNIT test_gauge_seconds seconds\n" + "# HELP test_gauge_seconds Hello, world!\n" - + "test_gauge_seconds 123 \\d+\n" + + $"test_gauge_seconds{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 123 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -137,7 +137,7 @@ public void GaugeOneDimension() Assert.Matches( ("^" + "# TYPE test_gauge gauge\n" - + "test_gauge{tagKey='tagValue'} 123 \\d+\n" + + $"test_gauge{{otel_scope_name='{Utils.GetCurrentMethodName()}',tagKey='tagValue'}} 123 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -164,7 +164,7 @@ public void GaugeBoolDimension() Assert.Matches( ("^" + "# TYPE test_gauge gauge\n" - + "test_gauge{tagKey='true'} 123 \\d+\n" + + $"test_gauge{{otel_scope_name='{Utils.GetCurrentMethodName()}',tagKey='true'}} 123 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -194,9 +194,9 @@ public void GaugeDoubleSubnormal() Assert.Matches( ("^" + "# TYPE test_gauge gauge\n" - + "test_gauge{x='1',y='2'} -Inf \\d+\n" - + "test_gauge{x='3',y='4'} \\+Inf \\d+\n" - + "test_gauge{x='5',y='6'} Nan \\d+\n" + + $"test_gauge{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2'}} -Inf \\d+\n" + + $"test_gauge{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='3',y='4'}} \\+Inf \\d+\n" + + $"test_gauge{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='5',y='6'}} Nan \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -223,7 +223,7 @@ public void SumDoubleInfinities() Assert.Matches( ("^" + "# TYPE test_counter_total counter\n" - + "test_counter_total \\+Inf \\d+\n" + + $"test_counter_total{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} \\+Inf \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -250,7 +250,7 @@ public void SumNonMonotonicDouble() Assert.Matches( ("^" + "# TYPE test_updown_counter gauge\n" - + "test_updown_counter -1 \\d+\n" + + $"test_updown_counter{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} -1 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -277,24 +277,24 @@ public void HistogramZeroDimension() Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" - + "test_histogram_bucket{le='0'} 0 \\d+\n" - + "test_histogram_bucket{le='5'} 0 \\d+\n" - + "test_histogram_bucket{le='10'} 0 \\d+\n" - + "test_histogram_bucket{le='25'} 1 \\d+\n" - + "test_histogram_bucket{le='50'} 1 \\d+\n" - + "test_histogram_bucket{le='75'} 1 \\d+\n" - + "test_histogram_bucket{le='100'} 2 \\d+\n" - + "test_histogram_bucket{le='250'} 2 \\d+\n" - + "test_histogram_bucket{le='500'} 2 \\d+\n" - + "test_histogram_bucket{le='750'} 2 \\d+\n" - + "test_histogram_bucket{le='1000'} 2 \\d+\n" - + "test_histogram_bucket{le='2500'} 2 \\d+\n" - + "test_histogram_bucket{le='5000'} 2 \\d+\n" - + "test_histogram_bucket{le='7500'} 2 \\d+\n" - + "test_histogram_bucket{le='10000'} 2 \\d+\n" - + "test_histogram_bucket{le='\\+Inf'} 2 \\d+\n" - + "test_histogram_sum 118 \\d+\n" - + "test_histogram_count 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='0'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='5'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='10'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='25'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='50'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='75'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='100'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='250'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='750'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='1000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='2500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='5000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='7500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='10000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='\\+Inf'}} 2 \\d+\n" + + $"test_histogram_sum{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 118 \\d+\n" + + $"test_histogram_count{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 2 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -321,24 +321,24 @@ public void HistogramOneDimension() Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" - + "test_histogram_bucket{x='1',le='0'} 0 \\d+\n" - + "test_histogram_bucket{x='1',le='5'} 0 \\d+\n" - + "test_histogram_bucket{x='1',le='10'} 0 \\d+\n" - + "test_histogram_bucket{x='1',le='25'} 1 \\d+\n" - + "test_histogram_bucket{x='1',le='50'} 1 \\d+\n" - + "test_histogram_bucket{x='1',le='75'} 1 \\d+\n" - + "test_histogram_bucket{x='1',le='100'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='250'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='500'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='750'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='1000'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='2500'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='5000'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='7500'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='10000'} 2 \\d+\n" - + "test_histogram_bucket{x='1',le='\\+Inf'} 2 \\d+\n" - + "test_histogram_sum{x='1'} 118 \\d+\n" - + "test_histogram_count{x='1'} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='0'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='5'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='10'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='25'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='50'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='75'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='100'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='250'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='750'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='1000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='2500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='5000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='7500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='10000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='\\+Inf'}} 2 \\d+\n" + + $"test_histogram_sum{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1'}} 118 \\d+\n" + + $"test_histogram_count{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1'}} 2 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -365,24 +365,24 @@ public void HistogramTwoDimensions() Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" - + "test_histogram_bucket{x='1',y='2',le='0'} 0 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='5'} 0 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='10'} 0 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='25'} 1 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='50'} 1 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='75'} 1 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='100'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='250'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='500'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='750'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='1000'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='2500'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='5000'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='7500'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='10000'} 2 \\d+\n" - + "test_histogram_bucket{x='1',y='2',le='\\+Inf'} 2 \\d+\n" - + "test_histogram_sum{x='1',y='2'} 118 \\d+\n" - + "test_histogram_count{x='1',y='2'} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='0'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='5'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='10'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='25'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='50'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='75'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='100'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='250'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='750'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='1000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='2500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='5000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='7500'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='10000'}} 2 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2',le='\\+Inf'}} 2 \\d+\n" + + $"test_histogram_sum{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2'}} 118 \\d+\n" + + $"test_histogram_count{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',y='2'}} 2 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -410,24 +410,24 @@ public void HistogramInfinities() Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" - + "test_histogram_bucket{le='0'} 0 \\d+\n" - + "test_histogram_bucket{le='5'} 0 \\d+\n" - + "test_histogram_bucket{le='10'} 0 \\d+\n" - + "test_histogram_bucket{le='25'} 1 \\d+\n" - + "test_histogram_bucket{le='50'} 1 \\d+\n" - + "test_histogram_bucket{le='75'} 1 \\d+\n" - + "test_histogram_bucket{le='100'} 1 \\d+\n" - + "test_histogram_bucket{le='250'} 1 \\d+\n" - + "test_histogram_bucket{le='500'} 1 \\d+\n" - + "test_histogram_bucket{le='750'} 1 \\d+\n" - + "test_histogram_bucket{le='1000'} 1 \\d+\n" - + "test_histogram_bucket{le='2500'} 1 \\d+\n" - + "test_histogram_bucket{le='5000'} 1 \\d+\n" - + "test_histogram_bucket{le='7500'} 1 \\d+\n" - + "test_histogram_bucket{le='10000'} 1 \\d+\n" - + "test_histogram_bucket{le='\\+Inf'} 3 \\d+\n" - + "test_histogram_sum \\+Inf \\d+\n" - + "test_histogram_count 3 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='0'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='5'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='10'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='25'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='50'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='75'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='100'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='250'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='500'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='750'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='1000'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='2500'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='5000'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='7500'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='10000'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='\\+Inf'}} 3 \\d+\n" + + $"test_histogram_sum{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} \\+Inf \\d+\n" + + $"test_histogram_count{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 3 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -455,24 +455,24 @@ public void HistogramNaN() Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" - + "test_histogram_bucket{le='0'} 0 \\d+\n" - + "test_histogram_bucket{le='5'} 0 \\d+\n" - + "test_histogram_bucket{le='10'} 0 \\d+\n" - + "test_histogram_bucket{le='25'} 1 \\d+\n" - + "test_histogram_bucket{le='50'} 1 \\d+\n" - + "test_histogram_bucket{le='75'} 1 \\d+\n" - + "test_histogram_bucket{le='100'} 1 \\d+\n" - + "test_histogram_bucket{le='250'} 1 \\d+\n" - + "test_histogram_bucket{le='500'} 1 \\d+\n" - + "test_histogram_bucket{le='750'} 1 \\d+\n" - + "test_histogram_bucket{le='1000'} 1 \\d+\n" - + "test_histogram_bucket{le='2500'} 1 \\d+\n" - + "test_histogram_bucket{le='5000'} 1 \\d+\n" - + "test_histogram_bucket{le='7500'} 1 \\d+\n" - + "test_histogram_bucket{le='10000'} 1 \\d+\n" - + "test_histogram_bucket{le='\\+Inf'} 3 \\d+\n" - + "test_histogram_sum Nan \\d+\n" - + "test_histogram_count 3 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='0'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='5'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='10'}} 0 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='25'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='50'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='75'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='100'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='250'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='500'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='750'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='1000'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='2500'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='5000'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='7500'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='10000'}} 1 \\d+\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',le='\\+Inf'}} 3 \\d+\n" + + $"test_histogram_sum{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} Nan \\d+\n" + + $"test_histogram_count{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} 3 \\d+\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -521,7 +521,7 @@ public void SumWithOpenMetricsFormat() Assert.Matches( ("^" + "# TYPE test_updown_counter gauge\n" - + "test_updown_counter -1 \\d+\\.\\d{3}\n" + + $"test_updown_counter{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} -1 \\d+\\.\\d{{3}}\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -548,24 +548,24 @@ public void HistogramOneDimensionWithOpenMetricsFormat() Assert.Matches( ("^" + "# TYPE test_histogram histogram\n" - + "test_histogram_bucket{x='1',le='0'} 0 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='5'} 0 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='10'} 0 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='25'} 1 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='50'} 1 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='75'} 1 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='100'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='250'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='500'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='750'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='1000'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='2500'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='5000'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='7500'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='10000'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_bucket{x='1',le='\\+Inf'} 2 \\d+\\.\\d{3}\n" - + "test_histogram_sum{x='1'} 118 \\d+\\.\\d{3}\n" - + "test_histogram_count{x='1'} 2 \\d+\\.\\d{3}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='0'}} 0 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='5'}} 0 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='10'}} 0 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='25'}} 1 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='50'}} 1 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='75'}} 1 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='100'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='250'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='500'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='750'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='1000'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='2500'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='5000'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='7500'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='10000'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_bucket{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1',le='\\+Inf'}} 2 \\d+\\.\\d{{3}}\n" + + $"test_histogram_sum{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1'}} 118 \\d+\\.\\d{{3}}\n" + + $"test_histogram_count{{otel_scope_name='{Utils.GetCurrentMethodName()}',x='1'}} 2 \\d+\\.\\d{{3}}\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } @@ -591,7 +591,7 @@ public void SumWithOpenScopeInfo() { var buffer = new byte[85000]; var metrics = new List(); - using var meter = new Meter(Utils.GetCurrentMethodName()); + using var meter = new Meter(Utils.GetCurrentMethodName(), "1.0.0"); using var provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) .AddInMemoryExporter(metrics) @@ -604,7 +604,7 @@ public void SumWithOpenScopeInfo() Assert.Matches( ("^" + "# TYPE test_updown_counter gauge\n" - + $"test_updown_counter{{otel_scope_name='{Utils.GetCurrentMethodName()}'}} -1 \\d+\\.\\d{{3}}\n" + + $"test_updown_counter{{otel_scope_name='{Utils.GetCurrentMethodName()}',otel_scope_version='1.0.0'}} -1 \\d+\\.\\d{{3}}\n" + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } From 0e1be380a2f49a8bb0d43465add649ad2c777cb4 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Sun, 10 Dec 2023 20:27:50 +1100 Subject: [PATCH 20/23] Test naming --- .../PrometheusSerializerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index f5b96d4912..55c4412b27 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -587,7 +587,7 @@ public void ScopeInfo() } [Fact] - public void SumWithOpenScopeInfo() + public void SumWithScopeVersion() { var buffer = new byte[85000]; var metrics = new List(); @@ -610,7 +610,7 @@ public void SumWithOpenScopeInfo() } [Fact] - public void HistogramOneDimensionWithScopeInfo() + public void HistogramOneDimensionWithScopeVersion() { var buffer = new byte[85000]; var metrics = new List(); From aa16875a8c1f63944226522d3cb1c8a764290018 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Fri, 15 Dec 2023 05:05:21 +1100 Subject: [PATCH 21/23] Updated changelog --- src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md | 2 +- src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index 22fbe7a4ce..ba08546437 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) -* Added ability to export otel_scope_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* For scrape requests with OpenMetrics format, otel_scope_info is automatically added ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) ## 1.7.0-rc.1 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index 55b7ca7fa8..bb8b59b31d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) -* Added ability to export otel_scope_info from Prometheus exporters ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* For scrape requests with OpenMetrics format, otel_scope_info is automatically added ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) ## 1.7.0-rc.1 From 22bfa08b64811e654a7761d52f5bc67af8c22274 Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Fri, 15 Dec 2023 05:28:17 +1100 Subject: [PATCH 22/23] Fix build --- src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md | 2 +- src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md index ba08546437..ca56db190d 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) -* For scrape requests with OpenMetrics format, otel_scope_info is automatically added ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* For requests with OpenMetrics format, scope info is automatically added ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) ## 1.7.0-rc.1 diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md index bb8b59b31d..ae8effc728 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased * Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107)) -* For scrape requests with OpenMetrics format, otel_scope_info is automatically added ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) +* For requests with OpenMetrics format, scope info is automatically added ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)) ## 1.7.0-rc.1 From 026cfeba1d952616421de95434ff6c76b7a25a0c Mon Sep 17 00:00:00 2001 From: Robert Coltheart Date: Fri, 15 Dec 2023 11:14:45 +1100 Subject: [PATCH 23/23] Combine scope writing with hashset --- .../Internal/PrometheusCollectionManager.cs | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index af1bd9f139..597ac5befb 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -178,29 +178,27 @@ private ExportResult OnCollect(Batch metrics) { if (PrometheusSerializer.CanWriteMetric(metric)) { - this.scopes.Add(metric.MeterName); - } - } - - foreach (var scope in this.scopes) - { - try - { - cursor = PrometheusSerializer.WriteScopeInfo(this.buffer, cursor, scope); - - break; - } - catch (IndexOutOfRangeException) - { - if (!this.IncreaseBufferSize()) + if (this.scopes.Add(metric.MeterName)) { - // there are two cases we might run into the following condition: - // 1. we have many metrics to be exported - in this case we probably want - // to put some upper limit and allow the user to configure it. - // 2. we got an IndexOutOfRangeException which was triggered by some other - // code instead of the buffer[cursor++] - in this case we should give up - // at certain point rather than allocating like crazy. - throw; + try + { + cursor = PrometheusSerializer.WriteScopeInfo(this.buffer, cursor, metric.MeterName); + + break; + } + catch (IndexOutOfRangeException) + { + if (!this.IncreaseBufferSize()) + { + // there are two cases we might run into the following condition: + // 1. we have many metrics to be exported - in this case we probably want + // to put some upper limit and allow the user to configure it. + // 2. we got an IndexOutOfRangeException which was triggered by some other + // code instead of the buffer[cursor++] - in this case we should give up + // at certain point rather than allocating like crazy. + throw; + } + } } } }