[Exporter.Prometheus] - enable analysis (#6171)
Co-authored-by: Rajkumar Rangaraj <rajrang@microsoft.com>
This commit is contained in:
parent
5635e6b8d6
commit
a8f1076e4d
|
@ -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
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<PackageTags>$(PackageTags);prometheus;metrics</PackageTags>
|
||||
<MinVerTagPrefix>coreunstable-</MinVerTagPrefix>
|
||||
<DefineConstants>$(DefineConstants);PROMETHEUS_ASPNETCORE</DefineConstants>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RunningDotNetPack)' != 'true'">
|
||||
|
|
|
@ -99,6 +99,8 @@ public static class PrometheusExporterApplicationBuilderExtensions
|
|||
Action<IApplicationBuilder>? configureBranchedPipeline,
|
||||
string? optionsName)
|
||||
{
|
||||
Guard.ThrowIfNull(app);
|
||||
|
||||
// Note: Order is important here. MeterProvider is accessed before
|
||||
// GetOptions<PrometheusAspNetCoreOptions> so that any changes made to
|
||||
// PrometheusAspNetCoreOptions in deferred AddPrometheusExporter
|
||||
|
@ -114,7 +116,7 @@ public static class PrometheusExporterApplicationBuilderExtensions
|
|||
path = options.ScrapeEndpointPath ?? PrometheusAspNetCoreOptions.DefaultScrapeEndpointPath;
|
||||
}
|
||||
|
||||
if (!path.StartsWith("/"))
|
||||
if (!path.StartsWith('/'))
|
||||
{
|
||||
path = $"/{path}";
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ public static class PrometheusExporterEndpointRouteBuilderExtensions
|
|||
Action<IApplicationBuilder>? 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 class PrometheusExporterEndpointRouteBuilderExtensions
|
|||
path = options.ScrapeEndpointPath ?? PrometheusAspNetCoreOptions.DefaultScrapeEndpointPath;
|
||||
}
|
||||
|
||||
if (!path.StartsWith("/"))
|
||||
if (!path.StartsWith('/'))
|
||||
{
|
||||
path = $"/{path}";
|
||||
}
|
||||
|
|
|
@ -62,9 +62,11 @@ public static class PrometheusExporterMeterProviderBuilderExtensions
|
|||
});
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -74,7 +74,7 @@ internal sealed class PrometheusExporterMiddleware
|
|||
? "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
|
||||
{
|
||||
|
|
|
@ -34,8 +34,8 @@ internal sealed class PrometheusCollectionManager
|
|||
this.exporter = exporter;
|
||||
this.scrapeResponseCacheDurationMilliseconds = this.exporter.ScrapeResponseCacheDurationMilliseconds;
|
||||
this.onCollectRef = this.OnCollect;
|
||||
this.metricsCache = new Dictionary<Metric, PrometheusMetric>();
|
||||
this.scopes = new HashSet<string>();
|
||||
this.metricsCache = [];
|
||||
this.scopes = [];
|
||||
}
|
||||
|
||||
#if NET
|
||||
|
@ -68,10 +68,7 @@ internal sealed class PrometheusCollectionManager
|
|||
// 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<CollectionResponse>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
this.collectionTcs ??= new TaskCompletionSource<CollectionResponse>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
Interlocked.Increment(ref this.readerCount);
|
||||
this.ExitGlobalLock();
|
||||
|
@ -148,6 +145,22 @@ internal sealed class PrometheusCollectionManager
|
|||
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 @@ internal sealed class PrometheusCollectionManager
|
|||
}
|
||||
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 @@ internal sealed class PrometheusCollectionManager
|
|||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
if (!this.IncreaseBufferSize(ref buffer))
|
||||
if (!IncreaseBufferSize(ref buffer))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
@ -285,7 +298,7 @@ internal sealed class PrometheusCollectionManager
|
|||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
if (!this.IncreaseBufferSize(ref buffer))
|
||||
if (!IncreaseBufferSize(ref buffer))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
@ -307,11 +320,11 @@ internal sealed class PrometheusCollectionManager
|
|||
{
|
||||
if (this.exporter.OpenMetricsRequested)
|
||||
{
|
||||
this.previousOpenMetricsDataView = new ArraySegment<byte>(Array.Empty<byte>(), 0, 0);
|
||||
this.previousOpenMetricsDataView = new ArraySegment<byte>([], 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.previousPlainTextDataView = new ArraySegment<byte>(Array.Empty<byte>(), 0, 0);
|
||||
this.previousPlainTextDataView = new ArraySegment<byte>([], 0, 0);
|
||||
}
|
||||
|
||||
return ExportResult.Failure;
|
||||
|
@ -332,7 +345,7 @@ internal sealed class PrometheusCollectionManager
|
|||
}
|
||||
catch (IndexOutOfRangeException)
|
||||
{
|
||||
if (!this.IncreaseBufferSize(ref buffer))
|
||||
if (!IncreaseBufferSize(ref buffer))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
@ -343,22 +356,6 @@ internal sealed class PrometheusCollectionManager
|
|||
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.
|
||||
|
|
|
@ -16,10 +16,10 @@ internal sealed class PrometheusMetric
|
|||
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 @@ internal sealed class PrometheusMetric
|
|||
// [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 @@ internal sealed class PrometheusMetric
|
|||
// 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 sealed class PrometheusMetric
|
|||
|
||||
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 sealed class PrometheusMetric
|
|||
|
||||
private static string SanitizeOpenMetricsName(string metricName)
|
||||
{
|
||||
if (metricName.EndsWith("_total"))
|
||||
if (metricName.EndsWith("_total", StringComparison.Ordinal))
|
||||
{
|
||||
return metricName.Substring(0, metricName.Length - 6);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<Description>Stand-alone HttpListener for hosting OpenTelemetry .NET Prometheus Exporter</Description>
|
||||
<PackageTags>$(PackageTags);prometheus;metrics</PackageTags>
|
||||
<MinVerTagPrefix>coreunstable-</MinVerTagPrefix>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RunningDotNetPack)' != 'true'">
|
||||
|
|
|
@ -30,12 +30,20 @@ internal sealed class PrometheusHttpListener : IDisposable
|
|||
|
||||
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 @@ internal sealed class PrometheusHttpListener : IDisposable
|
|||
? "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
|
||||
{
|
||||
|
|
|
@ -62,7 +62,7 @@ public static class PrometheusHttpListenerMeterProviderBuilderExtensions
|
|||
});
|
||||
}
|
||||
|
||||
private static MetricReader BuildPrometheusHttpListenerMetricReader(
|
||||
private static BaseExportingMetricReader BuildPrometheusHttpListenerMetricReader(
|
||||
PrometheusHttpListenerOptions options)
|
||||
{
|
||||
var exporter = new PrometheusExporter(new PrometheusExporterOptions
|
||||
|
@ -78,8 +78,10 @@ public static class PrometheusHttpListenerMeterProviderBuilderExtensions
|
|||
|
||||
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)
|
||||
|
|
|
@ -17,7 +17,6 @@ public class BaseExportingMetricReader : MetricReader
|
|||
/// </summary>
|
||||
protected readonly BaseExporter<Metric> 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 class BaseExportingMetricReader : MetricReader
|
|||
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 class BaseExportingMetricReader : MetricReader
|
|||
/// <summary>
|
||||
/// Gets the supported <see cref="ExportModes"/>.
|
||||
/// </summary>
|
||||
protected ExportModes SupportedExportModes => this.supportedExportModes;
|
||||
protected ExportModes SupportedExportModes { get; } = ExportModes.Push | ExportModes.Pull;
|
||||
|
||||
internal override void SetParentProvider(BaseProvider parentProvider)
|
||||
{
|
||||
|
@ -106,12 +105,12 @@ public class BaseExportingMetricReader : MetricReader
|
|||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<Description>Unit test project for Prometheus Exporter AspNetCore for OpenTelemetry</Description>
|
||||
<TargetFrameworks>$(TargetFrameworksForAspNetCoreTests)</TargetFrameworks>
|
||||
<DefineConstants>$(DefineConstants);PROMETHEUS_ASPNETCORE</DefineConstants>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -28,7 +29,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests\EventSourceTest.cs" Link="Includes\EventSourceTest.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests\EventSourceTests.cs" Link="Includes\EventSourceTests.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests\PrometheusCollectionManagerTests.cs" Link="Includes\PrometheusCollectionManagerTests.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Exporter.Prometheus.HttpListener.Tests\PrometheusSerializerTests.cs" Link="Includes\PrometheusSerializerTests.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 sealed class PrometheusExporterMiddlewareTests
|
|||
{
|
||||
if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable<string>? headers))
|
||||
{
|
||||
headers = Array.Empty<string>();
|
||||
headers = [];
|
||||
}
|
||||
|
||||
Assert.Equal("true", headers.FirstOrDefault());
|
||||
|
@ -139,7 +140,7 @@ public sealed class PrometheusExporterMiddlewareTests
|
|||
{
|
||||
if (!rsp.Headers.TryGetValues("X-MiddlewareExecuted", out IEnumerable<string>? headers))
|
||||
{
|
||||
headers = Array.Empty<string>();
|
||||
headers = [];
|
||||
}
|
||||
|
||||
Assert.Equal("true", headers.FirstOrDefault());
|
||||
|
@ -314,7 +315,7 @@ public sealed class PrometheusExporterMiddlewareTests
|
|||
|
||||
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 @@ public sealed class PrometheusExporterMiddlewareTests
|
|||
string acceptHeader = "application/openmetrics-text",
|
||||
KeyValuePair<string, object?>[]? 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 @@ public sealed class PrometheusExporterMiddlewareTests
|
|||
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 @@ public sealed class PrometheusExporterMiddlewareTests
|
|||
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 @@ public sealed class PrometheusExporterMiddlewareTests
|
|||
|
||||
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}");
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ using Xunit;
|
|||
|
||||
namespace OpenTelemetry.Exporter.Prometheus.Tests;
|
||||
|
||||
public class EventSourceTest
|
||||
public class EventSourceTests
|
||||
{
|
||||
[Fact]
|
||||
public void EventSourceTest_PrometheusExporterEventSource()
|
|
@ -4,9 +4,10 @@
|
|||
<Description>Unit test project for Prometheus Exporter HttpListener for OpenTelemetry</Description>
|
||||
<TargetFrameworks>$(TargetFrameworksForTests)</TargetFrameworks>
|
||||
<DefineConstants>$(DefineConstants);PROMETHEUS_HTTP_LISTENER</DefineConstants>
|
||||
<AnalysisLevel>latest-all</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="All" />
|
||||
|
|
|
@ -31,7 +31,9 @@ public sealed class PrometheusCollectionManagerTests
|
|||
#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 sealed class PrometheusCollectionManagerTests
|
|||
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 sealed class PrometheusCollectionManagerTests
|
|||
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 sealed class PrometheusCollectionManagerTests
|
|||
{
|
||||
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 sealed class PrometheusCollectionManagerTests
|
|||
}
|
||||
}
|
||||
|
||||
private class Response
|
||||
private sealed class Response
|
||||
{
|
||||
public PrometheusCollectionManager.CollectionResponse CollectionResponse;
|
||||
|
||||
|
|
|
@ -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 class PrometheusHttpListenerTests
|
|||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
TestPrometheusHttpListenerUriPrefixOptions(new string[] { });
|
||||
TestPrometheusHttpListenerUriPrefixOptions([]);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -63,25 +64,25 @@ public class PrometheusHttpListenerTests
|
|||
[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 class PrometheusHttpListenerTests
|
|||
new("meter2", "value2"),
|
||||
};
|
||||
|
||||
await this.RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, meterTags: tags);
|
||||
await RunPrometheusExporterHttpServerIntegrationTest(acceptHeader: string.Empty, meterTags: tags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -105,7 +106,7 @@ public class PrometheusHttpListenerTests
|
|||
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 class PrometheusHttpListenerTests
|
|||
{
|
||||
Random random = new Random();
|
||||
int retryAttempts = 5;
|
||||
int port = 0;
|
||||
string? address = null;
|
||||
|
||||
PrometheusExporter? exporter = null;
|
||||
|
@ -122,7 +122,9 @@ public class PrometheusHttpListenerTests
|
|||
// 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 class PrometheusHttpListenerTests
|
|||
var oneKb = new string('A', 1024);
|
||||
for (var x = 0; x < 8500; x++)
|
||||
{
|
||||
attributes.Add(new KeyValuePair<string, object>(x.ToString(), oneKb));
|
||||
attributes.Add(new KeyValuePair<string, object>(x.ToString(CultureInfo.InvariantCulture), oneKb));
|
||||
}
|
||||
|
||||
var provider = BuildMeterProvider(meter, attributes, out var address);
|
||||
|
@ -197,11 +199,11 @@ public class PrometheusHttpListenerTests
|
|||
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 @@ public class PrometheusHttpListenerTests
|
|||
{
|
||||
Random random = new Random();
|
||||
int retryAttempts = 5;
|
||||
int port = 0;
|
||||
string? generatedAddress = null;
|
||||
MeterProvider? provider = null;
|
||||
|
||||
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
|
||||
generatedAddress = $"http://localhost:{port}/";
|
||||
|
||||
try
|
||||
|
@ -252,17 +255,12 @@ public class PrometheusHttpListenerTests
|
|||
|
||||
address = generatedAddress!;
|
||||
|
||||
if (provider == null)
|
||||
{
|
||||
throw new InvalidOperationException("HttpListener could not be started");
|
||||
}
|
||||
|
||||
return provider;
|
||||
return provider ?? throw new InvalidOperationException("HttpListener could not be started");
|
||||
}
|
||||
|
||||
private async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", KeyValuePair<string, object?>[]? meterTags = null)
|
||||
private static async Task RunPrometheusExporterHttpServerIntegrationTest(bool skipMetrics = false, string acceptHeader = "application/openmetrics-text", KeyValuePair<string, object?>[]? meterTags = null)
|
||||
{
|
||||
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 @@ public class PrometheusHttpListenerTests
|
|||
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 @@ public class PrometheusHttpListenerTests
|
|||
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;
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ public sealed class LogRecordSharedPoolTests
|
|||
|
||||
using BatchLogRecordExportProcessor processor = new(new NoopExporter());
|
||||
|
||||
List<Task> tasks = new();
|
||||
List<Task> tasks = [];
|
||||
|
||||
for (int i = 0; i < Environment.ProcessorCount; i++)
|
||||
{
|
||||
|
@ -230,7 +230,7 @@ public sealed class LogRecordSharedPoolTests
|
|||
|
||||
var pool = LogRecordSharedPool.Current;
|
||||
|
||||
List<Task> tasks = new();
|
||||
List<Task> tasks = [];
|
||||
|
||||
for (int i = 0; i < Environment.ProcessorCount; i++)
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace OpenTelemetry.Tests;
|
||||
|
||||
|
@ -34,11 +35,11 @@ internal static class EventSourceTestHelper
|
|||
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 @@ internal static class EventSourceTestHelper
|
|||
{
|
||||
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 @@ internal static class EventSourceTestHelper
|
|||
methodName,
|
||||
expected,
|
||||
actual);
|
||||
throw new Exception(errorMessage);
|
||||
throw EqualException.ForMismatchedValuesWithError(expected, actual, banner: errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,6 +129,6 @@ internal static class EventSourceTestHelper
|
|||
private static IEnumerable<MethodInfo> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace OpenTelemetry.Tests;
|
|||
/// <summary>
|
||||
/// Event listener for testing event sources.
|
||||
/// </summary>
|
||||
internal class TestEventListener : EventListener
|
||||
internal sealed class TestEventListener : EventListener
|
||||
{
|
||||
/// <summary>Unique Id used to identify events from the test thread.</summary>
|
||||
private readonly Guid activityId;
|
||||
|
@ -66,6 +66,12 @@ internal class TestEventListener : EventListener
|
|||
this.events.Clear();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
this.eventWritten.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>Handler for event source writes.</summary>
|
||||
/// <param name="eventData">The event data that was written.</param>
|
||||
protected override void OnEventWritten(EventWrittenEventArgs eventData)
|
||||
|
|
|
@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
|
|||
|
||||
namespace OpenTelemetry.Tests;
|
||||
|
||||
internal class Utils
|
||||
internal static class Utils
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static string GetCurrentMethodName()
|
||||
|
|
Loading…
Reference in New Issue