From b412857ce3279780b529d997457987100f840f79 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Tue, 29 Oct 2024 10:47:14 +0000 Subject: [PATCH] Add support for monotonic sums, add more tests --- .../Internal/PrometheusSerializerExt.cs | 71 ++++- .../PrometheusExporterMiddlewareTests.cs | 298 +++++++++++++++--- 2 files changed, 323 insertions(+), 46 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 1f7c75a6f93..3fed1f4c669 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -1,6 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Diagnostics; using OpenTelemetry.Metrics; namespace OpenTelemetry.Exporter.Prometheus; @@ -28,8 +29,11 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteUnitMetadata(buffer, cursor, prometheusMetric, openMetricsRequested); cursor = WriteHelpMetadata(buffer, cursor, prometheusMetric, metric.Description, openMetricsRequested); + var isLong = metric.MetricType.IsLong(); if (!metric.MetricType.IsHistogram()) { + var isSum = metric.MetricType.IsSum(); + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds(); @@ -40,12 +44,9 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe buffer[cursor++] = unchecked((byte)' '); - // TODO: MetricType is same for all MetricPoints - // within a given Metric, so this check can avoided - // for each MetricPoint - if (((int)metric.MetricType & 0b_0000_1111) == 0x0a /* I8 */) + if (isLong) { - if (metric.MetricType.IsSum()) + if (isSum) { cursor = WriteLong(buffer, cursor, metricPoint.GetSumLong()); } @@ -56,7 +57,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe } else { - if (metric.MetricType.IsSum()) + if (isSum) { cursor = WriteDouble(buffer, cursor, metricPoint.GetSumDouble()); } @@ -70,11 +71,18 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe cursor = WriteTimestamp(buffer, cursor, timestamp, openMetricsRequested); + if (isSum && metricPoint.TryGetExemplars(out var exemplarCollection)) + { + cursor = WriteSumExemplar(buffer, cursor, metric, openMetricsRequested, exemplarCollection); + } + buffer[cursor++] = ASCII_LINEFEED; } } else { + Debug.Assert(!isLong, "Expected histogram metric to be of type `double`"); + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { var tags = metricPoint.Tags; @@ -115,7 +123,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe { if (exemplars.Current.DoubleValue <= histogramMeasurement.ExplicitBound) { - cursor = WriteExemplar(buffer, cursor, exemplars.Current, openMetricsRequested, metric.Name); + cursor = WriteExemplar(buffer, cursor, exemplars.Current, openMetricsRequested, metric.Name, isLong: false); } while (hasExemplar && exemplars.Current.DoubleValue <= histogramMeasurement.ExplicitBound) @@ -160,7 +168,44 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe return cursor; } - private static int WriteExemplar(byte[] buffer, int cursor, in Exemplar exemplar, bool openMetricsRequested, string metricName) + private static int WriteSumExemplar( + byte[] buffer, + int cursor, + in Metric metric, + bool openMetricsRequested, + in ReadOnlyExemplarCollection exemplarCollection) + { + var exemplars = exemplarCollection.GetEnumerator(); + if (!exemplars.MoveNext()) + { + return cursor; + } + + ref readonly Exemplar maxExemplar = ref exemplars.Current; + var isLong = metric.MetricType.IsLong(); + + while (exemplars.MoveNext()) + { + if (isLong) + { + if (exemplars.Current.LongValue >= maxExemplar.LongValue) + { + maxExemplar = ref exemplars.Current; + } + } + else + { + if (exemplars.Current.DoubleValue >= maxExemplar.DoubleValue) + { + maxExemplar = ref exemplars.Current; + } + } + } + + return WriteExemplar(buffer, cursor, maxExemplar, openMetricsRequested, metric.Name, isLong); + } + + private static int WriteExemplar(byte[] buffer, int cursor, in Exemplar exemplar, bool openMetricsRequested, string metricName, bool isLong) { buffer[cursor++] = unchecked((byte)' '); buffer[cursor++] = unchecked((byte)'#'); @@ -204,7 +249,15 @@ private static int WriteExemplar(byte[] buffer, int cursor, in Exemplar exemplar buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra. buffer[cursor++] = unchecked((byte)' '); - cursor = WriteDouble(buffer, cursor, exemplar.DoubleValue); + if (isLong) + { + cursor = WriteLong(buffer, cursor, exemplar.LongValue); + } + else + { + cursor = WriteDouble(buffer, cursor, exemplar.DoubleValue); + } + buffer[cursor++] = unchecked((byte)' '); cursor = WriteTimestamp(buffer, cursor, exemplar.Timestamp.ToUnixTimeMilliseconds(), openMetricsRequested); diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index feb60c80a32..85cef11c640 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -44,12 +44,13 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleHistog .Build(); using var host = await StartTestHostAsync( - app => app.UseOpenTelemetryPrometheusScrapingEndpoint()); + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + enableExemplars: true); using var meter = new Meter(MeterName, MeterVersion); // Due to the default histogram buckets, exemplars will only be recorded - // for some of the values (the largest value in each histogram buckets wins): + // for some of the values (the last recorded value for each buckets wins): // ✓ ✓ ✓ ✓ - - ✓ - ✓ ✓ var values = new double[] { 10, 20, 50, 100, 150, 200, 250, 300, 350, 10001 }; @@ -64,13 +65,13 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleHistog using var response = await client.SendAsync(new HttpRequestMessage { - RequestUri = new Uri(client.BaseAddress, "metrics"), + RequestUri = new Uri("/metrics", UriKind.Relative), Headers = { {"Accept", "application/openmetrics-text"}, }, }); - var text = (await response.Content.ReadAsStringAsync()).ReplaceLineEndings(); + var text = await response.Content.ReadAsStringAsync(); var expectedPattern = """ @@ -101,7 +102,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleHistog dbl_histogram_seconds_sum\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 11431 \d+\.\d{3} dbl_histogram_seconds_count\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 10 \d+\.\d{3} \# EOF - """.ReplaceLineEndings(); + """.ReplaceLineEndings("\n"); Assert.Matches(expectedPattern, text); @@ -118,7 +119,8 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleHistog .Build(); using var host = await StartTestHostAsync( - app => app.UseOpenTelemetryPrometheusScrapingEndpoint()); + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + enableExemplars: true); var beginTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -163,7 +165,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleHistog {"Accept", "application/openmetrics-text"}, }, }); - var text = (await response.Content.ReadAsStringAsync()).ReplaceLineEndings(); + var text = await response.Content.ReadAsStringAsync(); var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -196,7 +198,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleHistog dbl_histogram_seconds_sum\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 265.3 \d+\.\d{3} dbl_histogram_seconds_count\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 3 \d+\.\d{3} \# EOF - """.ReplaceLineEndings(); + """.ReplaceLineEndings("\n"); Assert.Matches(expectedPattern, text); @@ -230,7 +232,8 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_PersistBetwe { // Disable caching to test exemplars persisting between scrapes options.ScrapeResponseCacheDurationMilliseconds = 0; - }); + }, + enableExemplars: true); using var meter = new Meter(MeterName, MeterVersion); @@ -255,7 +258,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_PersistBetwe {"Accept", "application/openmetrics-text"}, }, }); - var text2 = (await response2.Content.ReadAsStringAsync()).ReplaceLineEndings(); + var text2 = await response2.Content.ReadAsStringAsync(); var expectedPattern = """ @@ -286,7 +289,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_PersistBetwe dbl_histogram_seconds_sum\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 90 \d+\.\d{3} dbl_histogram_seconds_count\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 1 \d+\.\d{3} \# EOF - """.ReplaceLineEndings(); + """.ReplaceLineEndings("\n"); Assert.Matches(expectedPattern, text2); @@ -303,7 +306,8 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_LongHistogra .Build(); using var host = await StartTestHostAsync( - app => app.UseOpenTelemetryPrometheusScrapingEndpoint()); + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + enableExemplars: true); var beginTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -341,7 +345,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_LongHistogra {"Accept", "application/openmetrics-text"}, }, }); - var text = (await response.Content.ReadAsStringAsync()).ReplaceLineEndings(); + var text = await response.Content.ReadAsStringAsync(); var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -374,7 +378,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_LongHistogra histogram_seconds_sum\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 265 \d+\.\d{3} histogram_seconds_count\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 3 \d+\.\d{3} \# EOF - """.ReplaceLineEndings(); + """.ReplaceLineEndings("\n"); Assert.Matches(expectedPattern, text); @@ -394,7 +398,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_LongHistogra } [Fact] - public async Task PrometheusExporterMiddlewareIntegration_Exemplars_Tags() + public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleHistogram_Tags() { using var activitySource = new ActivitySource(Utils.GetCurrentMethodName()); using var tracerProvider = Sdk.CreateTracerProviderBuilder() @@ -404,6 +408,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_Tags() using var host = await StartTestHostAsync( app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + enableExemplars: true, enableTagFiltering: true); using var meter = new Meter(MeterName, MeterVersion); @@ -451,7 +456,7 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_Tags() {"Accept", "application/openmetrics-text"}, }, }); - var text = (await response.Content.ReadAsStringAsync()).ReplaceLineEndings(); + var text = await response.Content.ReadAsStringAsync(); var expectedPattern = """ @@ -482,7 +487,222 @@ public async Task PrometheusExporterMiddlewareIntegration_Exemplars_Tags() dbl_histogram_seconds_sum\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 360 \d+\.\d{3} dbl_histogram_seconds_count\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 5 \d+\.\d{3} \# EOF - """.ReplaceLineEndings(); + """.ReplaceLineEndings("\n"); + + Assert.Matches(expectedPattern, text); + + await host.StopAsync(); + } + + [Fact] + public async Task PrometheusExporterMiddlewareIntegration_Exemplars_DoubleCounter() + { + using var activitySource = new ActivitySource(Utils.GetCurrentMethodName()); + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(activitySource.Name) + .SetSampler(new AlwaysOnSampler()) + .Build(); + + using var host = await StartTestHostAsync( + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + enableExemplars: true); + + using var meter = new Meter(MeterName, MeterVersion); + + var beginTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + + var counter = meter.CreateCounter("counter", unit: "s"); + using (activitySource.StartActivity("testActivity")) + { + counter.Add(123.456); + } + + string expectedTraceId; + string expectedSpanId; + using (var activity = activitySource.StartActivity("testActivity")) + { + counter.Add(78.9); + expectedTraceId = activity.TraceId.ToHexString(); + expectedSpanId = activity.SpanId.ToHexString(); + } + + using var client = host.GetTestClient(); + + using var response = await client.SendAsync(new HttpRequestMessage + { + RequestUri = new Uri("/metrics", UriKind.Relative), + Headers = + { + {"Accept", "application/openmetrics-text"}, + }, + }); + + var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + + var text = await response.Content.ReadAsStringAsync(); + + var expectedPattern = + """ + \# TYPE target info + \# HELP target Target metadata + target_info\{service_name="my_service",service_instance_id="id1"} 1 + \# TYPE otel_scope_info info + \# HELP otel_scope_info Scope metadata + otel_scope_info\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor"} 1 + \# TYPE counter_seconds counter + \# UNIT counter_seconds seconds + counter_seconds_total\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 202\.356 \d+\.\d{3} \# \{trace_id="([a-z0-9]{32})",span_id="([a-z0-9]{16})"} 78\.9 (\d+\.\d{3}) + \# EOF + """.ReplaceLineEndings("\n"); + + var match = Regex.Match(text, expectedPattern); + Assert.True(match.Success); + + Assert.Equal(expectedTraceId, match.Groups[1].Value); + Assert.Equal(expectedSpanId, match.Groups[2].Value); + + var timestamp = double.Parse(match.Groups[3].Value); + Assert.True(timestamp >= beginTimestamp / 1000.0 && timestamp <= endTimestamp / 1000.0); + + await host.StopAsync(); + } + + [Fact] + public async Task PrometheusExporterMiddlewareIntegration_Exemplars_LongCounter() + { + using var activitySource = new ActivitySource(Utils.GetCurrentMethodName()); + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(activitySource.Name) + .SetSampler(new AlwaysOnSampler()) + .Build(); + + using var host = await StartTestHostAsync( + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + enableExemplars: true); + + using var meter = new Meter(MeterName, MeterVersion); + + var beginTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + + var counter = meter.CreateCounter("counter", unit: "s"); + using (activitySource.StartActivity("testActivity")) + { + counter.Add(123L); + } + + string expectedTraceId; + string expectedSpanId; + using (var activity = activitySource.StartActivity("testActivity")) + { + counter.Add(78L); + expectedTraceId = activity.TraceId.ToHexString(); + expectedSpanId = activity.SpanId.ToHexString(); + } + + using var client = host.GetTestClient(); + + using var response = await client.SendAsync(new HttpRequestMessage + { + RequestUri = new Uri("/metrics", UriKind.Relative), + Headers = + { + {"Accept", "application/openmetrics-text"}, + }, + }); + + var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + + var text = await response.Content.ReadAsStringAsync(); + + var expectedPattern = + """ + \# TYPE target info + \# HELP target Target metadata + target_info\{service_name="my_service",service_instance_id="id1"} 1 + \# TYPE otel_scope_info info + \# HELP otel_scope_info Scope metadata + otel_scope_info\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor"} 1 + \# TYPE counter_seconds counter + \# UNIT counter_seconds seconds + counter_seconds_total\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 201 \d+\.\d{3} \# \{trace_id="([a-z0-9]{32})",span_id="([a-z0-9]{16})"} 78 (\d+\.\d{3}) + \# EOF + """.ReplaceLineEndings("\n"); + + var match = Regex.Match(text, expectedPattern); + Assert.True(match.Success); + + Assert.Equal(expectedTraceId, match.Groups[1].Value); + Assert.Equal(expectedSpanId, match.Groups[2].Value); + + var timestamp = double.Parse(match.Groups[3].Value); + Assert.True(timestamp >= beginTimestamp / 1000.0 && timestamp <= endTimestamp / 1000.0); + + await host.StopAsync(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task PrometheusExporterMiddlewareIntegration_Exemplars_LongCounter_Tags(bool enableTagFiltering) + { + using var activitySource = new ActivitySource(Utils.GetCurrentMethodName()); + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(activitySource.Name) + .SetSampler(new AlwaysOnSampler()) + .Build(); + + using var host = await StartTestHostAsync( + app => app.UseOpenTelemetryPrometheusScrapingEndpoint(), + enableExemplars: true, + enableTagFiltering: enableTagFiltering); + + using var meter = new Meter(MeterName, MeterVersion); + + var counter = meter.CreateCounter("counter", unit: "s"); + using (activitySource.StartActivity("testActivity")) + { + counter.Add(123L, new KeyValuePair("key1", "value1")); + } + + using var client = host.GetTestClient(); + + using var response = await client.SendAsync(new HttpRequestMessage + { + RequestUri = new Uri("/metrics", UriKind.Relative), + Headers = + { + {"Accept", "application/openmetrics-text"}, + }, + }); + + var text = await response.Content.ReadAsStringAsync(); + + var expectedPattern = + enableTagFiltering + ? """ + \# TYPE target info + \# HELP target Target metadata + target_info\{service_name="my_service",service_instance_id="id1"} 1 + \# TYPE otel_scope_info info + \# HELP otel_scope_info Scope metadata + otel_scope_info\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor"} 1 + \# TYPE counter_seconds counter + \# UNIT counter_seconds seconds + counter_seconds_total\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1"} 123 \d+\.\d{3} \# \{trace_id="[a-z0-9]{32}",span_id="[a-z0-9]{16}",key1="value1"} 123 \d+\.\d{3} + \# EOF + """.ReplaceLineEndings("\n") + : """ + \# TYPE target info + \# HELP target Target metadata + target_info\{service_name="my_service",service_instance_id="id1"} 1 + \# TYPE otel_scope_info info + \# HELP otel_scope_info Scope metadata + otel_scope_info\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor"} 1 + \# TYPE counter_seconds counter + \# UNIT counter_seconds seconds + counter_seconds_total\{otel_scope_name="OpenTelemetry\.Exporter\.Prometheus\.AspNetCore\.Tests\.PrometheusExporterMiddlewareTests\.\.cctor",otel_scope_version="1\.0\.1",key1="value1"} 123 \d+\.\d{3} \# \{trace_id="[a-z0-9]{32}",span_id="[a-z0-9]{16}"} 123 \d+\.\d{3} + \# EOF + """.ReplaceLineEndings("\n"); Assert.Matches(expectedPattern, text); @@ -826,12 +1046,13 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( bool registerMeterProvider = true, Action? configureOptions = null, bool skipMetrics = false, + bool enableExemplars = false, string acceptHeader = "application/openmetrics-text", KeyValuePair[]? meterTags = null) { var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); - using var host = await StartTestHostAsync(configure, configureServices, registerMeterProvider, false, configureOptions); + using var host = await StartTestHostAsync(configure, configureServices, registerMeterProvider, false, enableExemplars, configureOptions); var counterTags = new KeyValuePair[] { @@ -893,7 +1114,7 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht ? $"{string.Join(",", meterTags.Select(x => $"{x.Key}=\"{x.Value}\""))}," : string.Empty; - string content = (await response.Content.ReadAsStringAsync()).ReplaceLineEndings(); + string content = await response.Content.ReadAsStringAsync(); string expected = requestOpenMetrics ? $$""" @@ -908,14 +1129,14 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17 (\d+\.\d{3}) # EOF - """.ReplaceLineEndings() + """.ReplaceLineEndings("\n") : $$""" # TYPE counter_double_bytes_total counter # UNIT counter_double_bytes_total bytes counter_double_bytes_total{otel_scope_name="{{MeterName}}",otel_scope_version="{{MeterVersion}}",{{additionalTags}}key1="value1",key2="value2"} 101.17 (\d+) # EOF - """.ReplaceLineEndings(); + """.ReplaceLineEndings("\n"); var matches = Regex.Matches(content, "^" + expected + "$"); @@ -930,6 +1151,7 @@ private static Task StartTestHostAsync( Action configure, Action? configureServices = null, bool registerMeterProvider = true, + bool enableExemplars = false, bool enableTagFiltering = false, Action? configureOptions = null) { @@ -940,23 +1162,25 @@ private static Task StartTestHostAsync( { if (registerMeterProvider) { - services.AddOpenTelemetry().WithMetrics(builder => builder - .AddView(i => - { - return enableTagFiltering - ? new MetricStreamConfiguration - { - TagKeys = [], - } - : null; - }) - .ConfigureResource(x => x.Clear().AddService("my_service", serviceInstanceId: "id1")) - .AddMeter(MeterName) - .SetExemplarFilter(ExemplarFilterType.AlwaysOn) - .AddPrometheusExporter(o => + services.AddOpenTelemetry().WithMetrics(builder => + { + builder + .AddView(i => enableTagFiltering + ? new MetricStreamConfiguration {TagKeys = [],} + : null) + .ConfigureResource(x => x.Clear() + .AddService("my_service", serviceInstanceId: "id1")) + .AddMeter(MeterName) + .AddPrometheusExporter(o => + { + configureOptions?.Invoke(o); + }); + + if (enableExemplars) { - configureOptions?.Invoke(o); - })); + builder.SetExemplarFilter(ExemplarFilterType.AlwaysOn); + } + }); } configureServices?.Invoke(services);