diff --git a/.editorconfig b/.editorconfig index e9277261794..5f172a29b3a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -160,6 +160,9 @@ dotnet_diagnostic.RS0041.severity = suggestion # CA1515: Disable making types internal for Tests classes. It is required by xunit dotnet_diagnostic.CA1515.severity = none +# CA2007: Disable Consider calling ConfigureAwait on the awaited task. It is not working with xunit +dotnet_diagnostic.CA2007.severity = none + [**/obj/**.cs] generated_code = true diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj index f6a49fe6a64..c0b202cc640 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/OpenTelemetry.Exporter.Prometheus.AspNetCore.csproj @@ -7,6 +7,7 @@ $(PackageTags);prometheus;metrics coreunstable- $(DefineConstants);PROMETHEUS_ASPNETCORE + latest-all diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs index 38559e6b201..92bef6f1b59 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterApplicationBuilderExtensions.cs @@ -99,6 +99,8 @@ public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint( Action? configureBranchedPipeline, string? optionsName) { + Guard.ThrowIfNull(app); + // Note: Order is important here. MeterProvider is accessed before // GetOptions so that any changes made to // PrometheusAspNetCoreOptions in deferred AddPrometheusExporter @@ -114,7 +116,7 @@ public static IApplicationBuilder UseOpenTelemetryPrometheusScrapingEndpoint( path = options.ScrapeEndpointPath ?? PrometheusAspNetCoreOptions.DefaultScrapeEndpointPath; } - if (!path.StartsWith("/")) + if (!path.StartsWith('/')) { path = $"/{path}"; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs index 45639b378fc..6604935ebf0 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterEndpointRouteBuilderExtensions.cs @@ -69,6 +69,8 @@ public static IEndpointConventionBuilder MapPrometheusScrapingEndpoint( Action? configureBranchedPipeline, string? optionsName) { + Guard.ThrowIfNull(endpoints); + var builder = endpoints.CreateApplicationBuilder(); // Note: Order is important here. MeterProvider is accessed before @@ -84,7 +86,7 @@ public static IEndpointConventionBuilder MapPrometheusScrapingEndpoint( path = options.ScrapeEndpointPath ?? PrometheusAspNetCoreOptions.DefaultScrapeEndpointPath; } - if (!path.StartsWith("/")) + if (!path.StartsWith('/')) { path = $"/{path}"; } diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs index f73455d3b08..e3567d4ad63 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMeterProviderBuilderExtensions.cs @@ -62,9 +62,11 @@ public static MeterProviderBuilder AddPrometheusExporter( }); } - private static MetricReader BuildPrometheusExporterMetricReader(PrometheusAspNetCoreOptions options) + private static BaseExportingMetricReader BuildPrometheusExporterMetricReader(PrometheusAspNetCoreOptions options) { +#pragma warning disable CA2000 // Dispose objects before losing scope var exporter = new PrometheusExporter(options.ExporterOptions); +#pragma warning restore CA2000 // Dispose objects before losing scope return new BaseExportingMetricReader(exporter) { diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs index f91a93f66bb..863695b975f 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/PrometheusExporterMiddleware.cs @@ -74,7 +74,7 @@ public async Task InvokeAsync(HttpContext httpContext) ? "application/openmetrics-text; version=1.0.0; charset=utf-8" : "text/plain; charset=utf-8; version=0.0.4"; - await response.Body.WriteAsync(dataView.Array!, 0, dataView.Count).ConfigureAwait(false); + await response.Body.WriteAsync(dataView.Array.AsMemory(0, dataView.Count)).ConfigureAwait(false); } else { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs index 1c74e36bcf8..ce4c5384485 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs @@ -34,8 +34,8 @@ public PrometheusCollectionManager(PrometheusExporter exporter) this.exporter = exporter; this.scrapeResponseCacheDurationMilliseconds = this.exporter.ScrapeResponseCacheDurationMilliseconds; this.onCollectRef = this.OnCollect; - this.metricsCache = new Dictionary(); - this.scopes = new HashSet(); + this.metricsCache = []; + this.scopes = []; } #if NET @@ -68,10 +68,7 @@ public Task EnterCollect(bool openMetricsRequested) // If a collection is already running, return a task to wait on the result. if (this.collectionRunning) { - if (this.collectionTcs == null) - { - this.collectionTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } + this.collectionTcs ??= new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Interlocked.Increment(ref this.readerCount); this.ExitGlobalLock(); @@ -148,6 +145,22 @@ public void ExitCollect() Interlocked.Decrement(ref this.readerCount); } + private static bool IncreaseBufferSize(ref byte[] buffer) + { + var newBufferSize = buffer.Length * 2; + + if (newBufferSize > 100 * 1024 * 1024) + { + return false; + } + + var newBuffer = new byte[newBufferSize]; + buffer.CopyTo(newBuffer, 0); + buffer = newBuffer; + + return true; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnterGlobalLock() { @@ -230,7 +243,7 @@ private ExportResult OnCollect(in Batch metrics) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { // 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 @@ -268,7 +281,7 @@ private ExportResult OnCollect(in Batch metrics) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { throw; } @@ -285,7 +298,7 @@ private ExportResult OnCollect(in Batch metrics) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { throw; } @@ -307,11 +320,11 @@ private ExportResult OnCollect(in Batch metrics) { if (this.exporter.OpenMetricsRequested) { - this.previousOpenMetricsDataView = new ArraySegment(Array.Empty(), 0, 0); + this.previousOpenMetricsDataView = new ArraySegment([], 0, 0); } else { - this.previousPlainTextDataView = new ArraySegment(Array.Empty(), 0, 0); + this.previousPlainTextDataView = new ArraySegment([], 0, 0); } return ExportResult.Failure; @@ -332,7 +345,7 @@ private int WriteTargetInfo(ref byte[] buffer) } catch (IndexOutOfRangeException) { - if (!this.IncreaseBufferSize(ref buffer)) + if (!IncreaseBufferSize(ref buffer)) { throw; } @@ -343,22 +356,6 @@ private int WriteTargetInfo(ref byte[] buffer) return this.targetInfoBufferLength; } - private bool IncreaseBufferSize(ref byte[] buffer) - { - var newBufferSize = buffer.Length * 2; - - if (newBufferSize > 100 * 1024 * 1024) - { - return false; - } - - var newBuffer = new byte[newBufferSize]; - buffer.CopyTo(newBuffer, 0); - buffer = newBuffer; - - return true; - } - private PrometheusMetric GetPrometheusMetric(Metric metric) { // Optimize writing metrics with bounded cache that has pre-calculated Prometheus names. diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs index cc9176ea0c5..9f80d6dc5d3 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusMetric.cs @@ -16,10 +16,10 @@ Histogram becomes histogram UpDownCounter becomes gauge * https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md#otlp-metric-points-to-prometheus */ - private static readonly PrometheusType[] MetricTypes = new PrometheusType[] - { + private static readonly PrometheusType[] MetricTypes = + [ PrometheusType.Untyped, PrometheusType.Counter, PrometheusType.Gauge, PrometheusType.Summary, PrometheusType.Histogram, PrometheusType.Histogram, PrometheusType.Histogram, PrometheusType.Histogram, PrometheusType.Gauge, - }; + ]; public PrometheusMetric(string name, string unit, PrometheusType type, bool disableTotalNameSuffixForCounters) { @@ -40,7 +40,7 @@ public PrometheusMetric(string name, string unit, PrometheusType type, bool disa // [OpenMetrics UNIT metadata](https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#metricfamily) // and as a suffix to the metric name. The unit suffix comes before any type-specific suffixes. // https://github.com/open-telemetry/opentelemetry-specification/blob/3dfb383fe583e3b74a2365c5a1d90256b273ee76/specification/compatibility/prometheus_and_openmetrics.md#metric-metadata-1 - if (!sanitizedName.EndsWith(sanitizedUnit)) + if (!sanitizedName.EndsWith(sanitizedUnit, StringComparison.Ordinal)) { sanitizedName += $"_{sanitizedUnit}"; openMetricsName += $"_{sanitizedUnit}"; @@ -51,14 +51,14 @@ public PrometheusMetric(string name, string unit, PrometheusType type, bool disa // Exporters SHOULD provide a configuration option to disable the addition of `_total` suffixes. // https://github.com/open-telemetry/opentelemetry-specification/blob/b2f923fb1650dde1f061507908b834035506a796/specification/compatibility/prometheus_and_openmetrics.md#L286 // Note that we no longer append '_ratio' for units that are '1', see: https://github.com/open-telemetry/opentelemetry-specification/issues/4058 - if (type == PrometheusType.Counter && !sanitizedName.EndsWith("_total") && !disableTotalNameSuffixForCounters) + if (type == PrometheusType.Counter && !sanitizedName.EndsWith("_total", StringComparison.Ordinal) && !disableTotalNameSuffixForCounters) { sanitizedName += "_total"; } // For counters requested using OpenMetrics format, the MetricFamily name MUST be suffixed with '_total', regardless of the setting to disable the 'total' suffix. // https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1 - if (type == PrometheusType.Counter && !openMetricsName.EndsWith("_total")) + if (type == PrometheusType.Counter && !openMetricsName.EndsWith("_total", StringComparison.Ordinal)) { openMetricsName += "_total"; } @@ -127,7 +127,7 @@ internal static string SanitizeMetricName(string metricName) return sb?.ToString() ?? metricName; - static StringBuilder CreateStringBuilder(string name) => new StringBuilder(name.Length); + static StringBuilder CreateStringBuilder(string name) => new(name.Length); } internal static string RemoveAnnotations(string unit) @@ -179,7 +179,7 @@ internal static string RemoveAnnotations(string unit) private static string SanitizeOpenMetricsName(string metricName) { - if (metricName.EndsWith("_total")) + if (metricName.EndsWith("_total", StringComparison.Ordinal)) { return metricName.Substring(0, metricName.Length - 6); } diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs index 54cbfac4170..c199a6695c5 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs @@ -17,8 +17,6 @@ internal static partial class PrometheusSerializer { #pragma warning disable SA1310 // Field name should not contain an underscore private const byte ASCII_QUOTATION_MARK = 0x22; // '"' - private const byte ASCII_FULL_STOP = 0x2E; // '.' - private const byte ASCII_HYPHEN_MINUS = 0x2D; // '-' private const byte ASCII_REVERSE_SOLIDUS = 0x5C; // '\\' private const byte ASCII_LINEFEED = 0x0A; // `\n` #pragma warning restore SA1310 // Field name should not contain an underscore diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj index 4e087919be2..a886b62ed65 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/OpenTelemetry.Exporter.Prometheus.HttpListener.csproj @@ -5,6 +5,7 @@ Stand-alone HttpListener for hosting OpenTelemetry .NET Prometheus Exporter $(PackageTags);prometheus;metrics coreunstable- + latest-all diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs index cecda73f7c9..b3bdf286f34 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListener.cs @@ -30,12 +30,20 @@ public PrometheusHttpListener(PrometheusExporter exporter, PrometheusHttpListene string path = options.ScrapeEndpointPath ?? PrometheusHttpListenerOptions.DefaultScrapeEndpointPath; - if (!path.StartsWith("/")) +#if NET + if (!path.StartsWith('/')) +#else + if (!path.StartsWith("/", StringComparison.Ordinal)) +#endif { path = $"/{path}"; } - if (!path.EndsWith("/")) +#if NET + if (!path.EndsWith('/')) +#else + if (!path.EndsWith("/", StringComparison.Ordinal)) +#endif { path = $"{path}/"; } @@ -164,7 +172,11 @@ private async Task ProcessRequestAsync(HttpListenerContext context) ? "application/openmetrics-text; version=1.0.0; charset=utf-8" : "text/plain; charset=utf-8; version=0.0.4"; - await context.Response.OutputStream.WriteAsync(dataView.Array!, 0, dataView.Count).ConfigureAwait(false); +#if NET + await context.Response.OutputStream.WriteAsync(dataView.Array.AsMemory(0, dataView.Count)).ConfigureAwait(false); +#else + await context.Response.OutputStream.WriteAsync(dataView.Array, 0, dataView.Count).ConfigureAwait(false); +#endif } else { diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs index 7289432cdcf..94eea0f5742 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/PrometheusHttpListenerMeterProviderBuilderExtensions.cs @@ -62,7 +62,7 @@ public static MeterProviderBuilder AddPrometheusHttpListener( }); } - private static MetricReader BuildPrometheusHttpListenerMetricReader( + private static BaseExportingMetricReader BuildPrometheusHttpListenerMetricReader( PrometheusHttpListenerOptions options) { var exporter = new PrometheusExporter(new PrometheusExporterOptions @@ -78,8 +78,10 @@ private static MetricReader BuildPrometheusHttpListenerMetricReader( try { +#pragma warning disable CA2000 // Dispose objects before losing scope var listener = new PrometheusHttpListener(exporter, options); - exporter.OnDispose = () => listener.Dispose(); +#pragma warning restore CA2000 // Dispose objects before losing scope + exporter.OnDispose = listener.Dispose; listener.Start(); } catch (Exception ex) diff --git a/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs b/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs index 80f96cc143a..eeaa3fc68c5 100644 --- a/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs +++ b/src/OpenTelemetry/Metrics/Reader/BaseExportingMetricReader.cs @@ -17,7 +17,6 @@ public class BaseExportingMetricReader : MetricReader /// protected readonly BaseExporter exporter; - private readonly ExportModes supportedExportModes = ExportModes.Push | ExportModes.Pull; private readonly string exportCalledMessage; private readonly string exportSucceededMessage; private readonly string exportFailedMessage; @@ -38,12 +37,12 @@ public BaseExportingMetricReader(BaseExporter exporter) if (attributes.Length > 0) { var attr = (ExportModesAttribute)attributes[attributes.Length - 1]; - this.supportedExportModes = attr.Supported; + this.SupportedExportModes = attr.Supported; } if (exporter is IPullMetricExporter pullExporter) { - if (this.supportedExportModes.HasFlag(ExportModes.Push)) + if (this.SupportedExportModes.HasFlag(ExportModes.Push)) { pullExporter.Collect = this.Collect; } @@ -69,7 +68,7 @@ public BaseExportingMetricReader(BaseExporter exporter) /// /// Gets the supported . /// - protected ExportModes SupportedExportModes => this.supportedExportModes; + protected ExportModes SupportedExportModes { get; } = ExportModes.Push | ExportModes.Pull; internal override void SetParentProvider(BaseProvider parentProvider) { @@ -106,12 +105,12 @@ internal override bool ProcessMetrics(in Batch metrics, int timeoutMilli /// protected override bool OnCollect(int timeoutMilliseconds) { - if (this.supportedExportModes.HasFlag(ExportModes.Push)) + if (this.SupportedExportModes.HasFlag(ExportModes.Push)) { return base.OnCollect(timeoutMilliseconds); } - if (this.supportedExportModes.HasFlag(ExportModes.Pull) && PullMetricScope.IsPullAllowed) + if (this.SupportedExportModes.HasFlag(ExportModes.Pull) && PullMetricScope.IsPullAllowed) { return base.OnCollect(timeoutMilliseconds); } diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj index 6c4ea8cb626..8c37feb0e25 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests.csproj @@ -4,6 +4,7 @@ Unit test project for Prometheus Exporter AspNetCore for OpenTelemetry $(TargetFrameworksForAspNetCoreTests) $(DefineConstants);PROMETHEUS_ASPNETCORE + latest-all @@ -28,7 +29,7 @@ - + diff --git a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs index c34dba29582..98d503e8e92 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.AspNetCore.Tests/PrometheusExporterMiddlewareTests.cs @@ -3,6 +3,7 @@ #if !NETFRAMEWORK using System.Diagnostics.Metrics; +using System.Globalization; using System.Net; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Builder; @@ -112,7 +113,7 @@ public Task PrometheusExporterMiddlewareIntegration_MixedPredicateAndPath() { if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable? headers)) { - headers = Array.Empty(); + headers = []; } Assert.Equal("true", headers.FirstOrDefault()); @@ -139,7 +140,7 @@ public Task PrometheusExporterMiddlewareIntegration_MixedPath() { if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable? headers)) { - headers = Array.Empty(); + headers = []; } Assert.Equal("true", headers.FirstOrDefault()); @@ -314,7 +315,7 @@ public async Task PrometheusExporterMiddlewareIntegration_TestBufferSizeIncrease using var client = host.GetTestClient(); - using var response = await client.GetAsync("/metrics"); + using var response = await client.GetAsync(new Uri("/metrics", UriKind.Relative)); var text = await response.Content.ReadAsStringAsync(); Assert.NotEmpty(text); @@ -372,7 +373,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( string acceptHeader = "application/openmetrics-text", KeyValuePair[]? meterTags = null) { - var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); + var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text", StringComparison.Ordinal); using var host = await StartTestHostAsync(configure, configureServices, registerMeterProvider, configureOptions); @@ -400,7 +401,7 @@ private static async Task RunPrometheusExporterMiddlewareIntegrationTest( client.DefaultRequestHeaders.Add("Accept", acceptHeader); } - using var response = await client.GetAsync(path); + using var response = await client.GetAsync(new Uri(path, UriKind.Relative)); var endTimestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -432,7 +433,7 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType!.ToString()); } - var additionalTags = meterTags != null && meterTags.Any() + var additionalTags = meterTags is { Length: > 0 } ? $"{string.Join(",", meterTags.Select(x => $"{x.Key}=\"{x.Value}\""))}," : string.Empty; @@ -464,7 +465,7 @@ private static async Task VerifyAsync(long beginTimestamp, long endTimestamp, Ht Assert.True(matches.Count == 1, content); - var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty)); + var timestamp = long.Parse(matches[0].Groups[1].Value.Replace(".", string.Empty, StringComparison.Ordinal), CultureInfo.InvariantCulture); Assert.True(beginTimestamp <= timestamp && timestamp <= endTimestamp, $"{beginTimestamp} {timestamp} {endTimestamp}"); } diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTest.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTests.cs similarity index 92% rename from test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTest.cs rename to test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTests.cs index baf8dc43377..b1aed407d63 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTest.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/EventSourceTests.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Exporter.Prometheus.Tests; -public class EventSourceTest +public class EventSourceTests { [Fact] public void EventSourceTest_PrometheusExporterEventSource() diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj index 6d6c38ff489..cc044afe37f 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests.csproj @@ -4,9 +4,10 @@ Unit test project for Prometheus Exporter HttpListener for OpenTelemetry $(TargetFrameworksForTests) $(DefineConstants);PROMETHEUS_HTTP_LISTENER + latest-all - + diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs index 36f5e124e67..75e3261b71a 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusCollectionManagerTests.cs @@ -31,7 +31,9 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon #endif .Build()) { +#pragma warning disable CA2000 // Dispose objects before losing scope if (!provider.TryFindExporter(out PrometheusExporter? exporter)) +#pragma warning restore CA2000 // Dispose objects before losing scope { throw new InvalidOperationException("PrometheusExporter could not be found on MeterProvider."); } @@ -60,7 +62,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon return new Response { CollectionResponse = response, - ViewPayload = openMetricsRequested ? response.OpenMetricsView.ToArray() : response.PlainTextView.ToArray(), + ViewPayload = openMetricsRequested ? [.. response.OpenMetricsView] : [.. response.PlainTextView], }; } finally @@ -110,7 +112,10 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon exporter.CollectionManager.ExitCollect(); } +#pragma warning disable CA1849 // 'Thread.Sleep(int)' synchronously blocks. Use await instead. + // Changing to await Task.Delay leads to test instability. Thread.Sleep(exporter.ScrapeResponseCacheDurationMilliseconds); +#pragma warning restore CA1849 // 'Thread.Sleep(int)' synchronously blocks. Use await instead. counter.Add(100); @@ -118,13 +123,13 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon { collectTasks[i] = Task.Run(async () => { - var response = await exporter.CollectionManager.EnterCollect(openMetricsRequested); + var collectionResponse = await exporter.CollectionManager.EnterCollect(openMetricsRequested); try { return new Response { - CollectionResponse = response, - ViewPayload = openMetricsRequested ? response.OpenMetricsView.ToArray() : response.PlainTextView.ToArray(), + CollectionResponse = collectionResponse, + ViewPayload = openMetricsRequested ? [.. collectionResponse.OpenMetricsView] : [.. collectionResponse.PlainTextView], }; } finally @@ -152,7 +157,7 @@ public async Task EnterExitCollectTest(int scrapeResponseCacheDurationMillisecon } } - private class Response + private sealed class Response { public PrometheusCollectionManager.CollectionResponse CollectionResponse; diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs index 0ac04d2f299..7ff58940692 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusHttpListenerTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.Metrics; +using System.Globalization; using System.Net; #if NETFRAMEWORK using System.Net.Http; @@ -47,7 +48,7 @@ public void UriPrefixesEmptyList() { Assert.Throws(() => { - TestPrometheusHttpListenerUriPrefixOptions(new string[] { }); + TestPrometheusHttpListenerUriPrefixOptions([]); }); } @@ -63,25 +64,25 @@ public void UriPrefixesInvalid() [Fact] public async Task PrometheusExporterHttpServerIntegration() { - await this.RunPrometheusExporterHttpServerIntegrationTest(); + await RunPrometheusExporterHttpServerIntegrationTest(); } [Fact] public async Task PrometheusExporterHttpServerIntegration_NoMetrics() { - await this.RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true); + await RunPrometheusExporterHttpServerIntegrationTest(skipMetrics: true); } [Fact] public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics() { - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty); } [Fact] public async Task PrometheusExporterHttpServerIntegration_UseOpenMetricsVersionHeader() { - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0"); } [Fact] @@ -93,7 +94,7 @@ public async Task PrometheusExporterHttpServerIntegration_NoOpenMetrics_WithMete new("meter2", "value2"), }; - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, meterTags: tags); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, meterTags: tags); } [Fact] @@ -105,7 +106,7 @@ public async Task PrometheusExporterHttpServerIntegration_OpenMetrics_WithMeterT new("meter2", "value2"), }; - await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0", meterTags: tags); + await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: "application/openmetrics-text; version=1.0.0", meterTags: tags); } [Fact] @@ -113,7 +114,6 @@ public void PrometheusHttpListenerThrowsOnStart() { Random random = new Random(); int retryAttempts = 5; - int port = 0; string? address = null; PrometheusExporter? exporter = null; @@ -122,7 +122,9 @@ public void PrometheusHttpListenerThrowsOnStart() // Step 1: Start a listener on a random port. while (retryAttempts-- != 0) { - port = random.Next(2000, 5000); +#pragma warning disable CA5394 // Do not use insecure randomness + int port = random.Next(2000, 5000); +#pragma warning restore CA5394 // Do not use insecure randomness address = $"http://localhost:{port}/"; try @@ -179,7 +181,7 @@ public async Task PrometheusExporterHttpServerIntegration_TestBufferSizeIncrease var oneKb = new string('A', 1024); for (var x = 0; x < 8500; x++) { - attributes.Add(new KeyValuePair(x.ToString(), oneKb)); + attributes.Add(new KeyValuePair(x.ToString(CultureInfo.InvariantCulture), oneKb)); } var provider = BuildMeterProvider(meter, attributes, out var address); @@ -197,11 +199,11 @@ public async Task PrometheusExporterHttpServerIntegration_TestBufferSizeIncrease client.DefaultRequestHeaders.Add("Accept", acceptHeader); } - using var response = await client.GetAsync($"{address}metrics"); + using var response = await client.GetAsync(new Uri($"{address}metrics")); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var content = await response.Content.ReadAsStringAsync(); - Assert.Contains("counter_double_999", content); + Assert.Contains("counter_double_999", content, StringComparison.Ordinal); Assert.DoesNotContain('\0', content); provider.Dispose(); @@ -222,13 +224,14 @@ private static MeterProvider BuildMeterProvider(Meter meter, IEnumerable[]? meterTags = null) + private static async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", KeyValuePair[]? meterTags = null) { - var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text"); + var requestOpenMetrics = acceptHeader.StartsWith("application/openmetrics-text", StringComparison.Ordinal); using var meter = new Meter(MeterName, MeterVersion, meterTags); @@ -288,7 +286,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri client.DefaultRequestHeaders.Add("Accept", acceptHeader); } - using var response = await client.GetAsync($"{address}metrics"); + using var response = await client.GetAsync(new Uri($"{address}metrics")); if (!skipMetrics) { @@ -304,7 +302,7 @@ private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetri Assert.Equal("text/plain; charset=utf-8; version=0.0.4", response.Content.Headers.ContentType!.ToString()); } - var additionalTags = meterTags != null && meterTags.Any() + var additionalTags = meterTags is { Length: > 0 } ? $"{string.Join(",", meterTags.Select(x => $"{x.Key}='{x.Value}'"))}," : string.Empty; diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs index d38c82d1a0c..3da599ad2b9 100644 --- a/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs +++ b/test/OpenTelemetry.Tests/Logs/LogRecordSharedPoolTests.cs @@ -170,7 +170,7 @@ public async Task ExportTest(bool warmup) using BatchLogRecordExportProcessor processor = new(new NoopExporter()); - List tasks = new(); + List tasks = []; for (int i = 0; i < Environment.ProcessorCount; i++) { @@ -230,7 +230,7 @@ public async Task DeadlockTest() var pool = LogRecordSharedPool.Current; - List tasks = new(); + List tasks = []; for (int i = 0; i < Environment.ProcessorCount; i++) { diff --git a/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs b/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs index d7066c3195c..8b30f7694b1 100644 --- a/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs +++ b/test/OpenTelemetry.Tests/Shared/EventSourceTestHelper.cs @@ -4,6 +4,7 @@ using System.Diagnostics.Tracing; using System.Globalization; using System.Reflection; +using Xunit.Sdk; namespace OpenTelemetry.Tests; @@ -34,11 +35,11 @@ private static void VerifyMethodImplementation(EventSource eventSource, MethodIn actualEvent = listener.Messages.FirstOrDefault(x => x.EventId == 0); if (actualEvent != null) { - throw new Exception(actualEvent.Message); + throw new InvalidOperationException(actualEvent.Message); } // give up - throw new Exception("Listener failed to collect event."); + throw new InvalidOperationException("Listener failed to collect event."); } VerifyEventId(eventMethod, actualEvent); @@ -49,7 +50,7 @@ private static void VerifyMethodImplementation(EventSource eventSource, MethodIn { var name = eventMethod.DeclaringType?.Name + "." + eventMethod.Name; - throw new Exception("Method '" + name + "' is implemented incorrectly.", e); + throw new InvalidOperationException("Method '" + name + "' is implemented incorrectly.", e); } finally { @@ -116,7 +117,7 @@ private static void AssertEqual(string methodName, T expected, T actual) methodName, expected, actual); - throw new Exception(errorMessage); + throw EqualException.ForMismatchedValuesWithError(expected, actual, banner: errorMessage); } } @@ -128,6 +129,6 @@ private static EventAttribute GetEventAttribute(MethodInfo eventMethod) private static IEnumerable GetEventMethods(EventSource eventSource) { MethodInfo[] methods = eventSource.GetType().GetMethods(); - return methods.Where(m => m.GetCustomAttributes(typeof(EventAttribute), false).Any()); + return methods.Where(m => m.GetCustomAttributes(typeof(EventAttribute), false).Length > 0); } } diff --git a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs index fb497190f5e..572ce1a6c4b 100644 --- a/test/OpenTelemetry.Tests/Shared/TestEventListener.cs +++ b/test/OpenTelemetry.Tests/Shared/TestEventListener.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.Tests; /// /// Event listener for testing event sources. /// -internal class TestEventListener : EventListener +internal sealed class TestEventListener : EventListener { /// Unique Id used to identify events from the test thread. private readonly Guid activityId; @@ -66,6 +66,12 @@ public void ClearMessages() this.events.Clear(); } + public override void Dispose() + { + this.eventWritten.Dispose(); + base.Dispose(); + } + /// Handler for event source writes. /// The event data that was written. protected override void OnEventWritten(EventWrittenEventArgs eventData) diff --git a/test/OpenTelemetry.Tests/Shared/Utils.cs b/test/OpenTelemetry.Tests/Shared/Utils.cs index d85c8c74e47..95f24821d0e 100644 --- a/test/OpenTelemetry.Tests/Shared/Utils.cs +++ b/test/OpenTelemetry.Tests/Shared/Utils.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Tests; -internal class Utils +internal static class Utils { [MethodImpl(MethodImplOptions.NoInlining)] public static string GetCurrentMethodName()