From 2f4574f6c0ba0b60bd593003388c527386e34da6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 6 Jun 2023 16:23:07 +0800 Subject: [PATCH 1/9] Add routing metrics --- .../EndpointRoutingShortCircuitBenchmark.cs | 5 +- ....AspNetCore.Routing.Microbenchmarks.csproj | 4 +- .../Routing/src/EndpointRoutingMiddleware.cs | 26 +++- src/Http/Routing/src/RoutingMetrics.cs | 45 ++++++ .../EndpointRoutingMiddlewareTest.cs | 4 +- .../Microsoft.AspNetCore.Routing.Tests.csproj | 3 + .../test/UnitTests/RoutingMetricsTests.cs | 146 ++++++++++++++++++ 7 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 src/Http/Routing/src/RoutingMetrics.cs create mode 100644 src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs diff --git a/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingShortCircuitBenchmark.cs b/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingShortCircuitBenchmark.cs index 9fc01b45acde..2adc33b73084 100644 --- a/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingShortCircuitBenchmark.cs +++ b/src/Http/Routing/perf/Microbenchmarks/EndpointRoutingShortCircuitBenchmark.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.ShortCircuit; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; @@ -21,6 +22,7 @@ public class EndpointRoutingShortCircuitBenchmark [GlobalSetup] public void Setup() { + var routingMetrics = new RoutingMetrics(new TestMeterFactory()); var normalEndpoint = new Endpoint(context => Task.CompletedTask, new EndpointMetadataCollection(), "normal"); _normalEndpointMiddleware = new EndpointRoutingMiddleware( @@ -30,6 +32,7 @@ public void Setup() new BenchmarkEndpointDataSource(), new DiagnosticListener("benchmark"), Options.Create(new RouteOptions()), + routingMetrics, context => Task.CompletedTask); var shortCircuitEndpoint = new Endpoint(context => Task.CompletedTask, new EndpointMetadataCollection(new ShortCircuitMetadata(200)), "shortcircuit"); @@ -41,8 +44,8 @@ public void Setup() new BenchmarkEndpointDataSource(), new DiagnosticListener("benchmark"), Options.Create(new RouteOptions()), + routingMetrics, context => Task.CompletedTask); - } [Benchmark] diff --git a/src/Http/Routing/perf/Microbenchmarks/Microsoft.AspNetCore.Routing.Microbenchmarks.csproj b/src/Http/Routing/perf/Microbenchmarks/Microsoft.AspNetCore.Routing.Microbenchmarks.csproj index 7ad295bd85c0..872e85f13ace 100644 --- a/src/Http/Routing/perf/Microbenchmarks/Microsoft.AspNetCore.Routing.Microbenchmarks.csproj +++ b/src/Http/Routing/perf/Microbenchmarks/Microsoft.AspNetCore.Routing.Microbenchmarks.csproj @@ -1,4 +1,4 @@ - + $(DefaultNetCoreTargetFramework) @@ -31,6 +31,7 @@ Matching\TreeRouterMatcherBuilder.cs + @@ -39,6 +40,7 @@ + diff --git a/src/Http/Routing/src/EndpointRoutingMiddleware.cs b/src/Http/Routing/src/EndpointRoutingMiddleware.cs index 175cf42cbc7f..6238c067f2e9 100644 --- a/src/Http/Routing/src/EndpointRoutingMiddleware.cs +++ b/src/Http/Routing/src/EndpointRoutingMiddleware.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.ShortCircuit; using Microsoft.Extensions.Logging; @@ -22,6 +23,7 @@ internal sealed partial class EndpointRoutingMiddleware private readonly ILogger _logger; private readonly EndpointDataSource _endpointDataSource; private readonly DiagnosticListener _diagnosticListener; + private readonly RoutingMetrics _metrics; private readonly RequestDelegate _next; private readonly RouteOptions _routeOptions; private Task? _initializationTask; @@ -33,6 +35,7 @@ public EndpointRoutingMiddleware( EndpointDataSource rootCompositeEndpointDataSource, DiagnosticListener diagnosticListener, IOptions routeOptions, + RoutingMetrics metrics, RequestDelegate next) { ArgumentNullException.ThrowIfNull(endpointRouteBuilder); @@ -40,6 +43,7 @@ public EndpointRoutingMiddleware( _matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener)); + _metrics = metrics; _next = next ?? throw new ArgumentNullException(nameof(next)); _routeOptions = routeOptions.Value; @@ -98,6 +102,7 @@ private Task SetRoutingAndContinue(HttpContext httpContext) if (endpoint == null) { Log.MatchFailure(_logger); + _metrics.MatchFailure(); } else { @@ -107,12 +112,21 @@ private Task SetRoutingAndContinue(HttpContext httpContext) Write(_diagnosticListener, httpContext); } - Log.MatchSuccess(_logger, endpoint); - - if (_logger.IsEnabled(LogLevel.Debug) - && endpoint.Metadata.GetMetadata() is not null) + if (_logger.IsEnabled(LogLevel.Debug) || _metrics.MatchSuccessCounterEnabled) { - Log.FallbackMatch(_logger, endpoint); + var isFallback = endpoint.Metadata.GetMetadata() is not null; + + Log.MatchSuccess(_logger, endpoint); + + if (isFallback) + { + Log.FallbackMatch(_logger, endpoint); + } + + // It shouldn't be possible for a route to be matched via the route matcher and not have a route. + // Just in case, add a special (unknown) value as the route tag to metrics. + var route = endpoint.Metadata.GetMetadata()?.Route ?? "(unknown)"; + _metrics.MatchSuccess(route, isFallback); } var shortCircuitMetadata = endpoint.Metadata.GetMetadata(); @@ -290,7 +304,7 @@ public static void MatchSkipped(ILogger logger, Endpoint endpoint) [LoggerMessage(6, LogLevel.Information, "The endpoint '{EndpointName}' is being short circuited without running additional middleware or producing a response.", EventName = "ShortCircuitedEndpoint")] public static partial void ShortCircuitedEndpoint(ILogger logger, Endpoint endpointName); - [LoggerMessage(7, LogLevel.Debug, "Matched endpoint '{EndpointName}' is a fallback endpoint.", EventName = "FallbackMatch", SkipEnabledCheck = true)] + [LoggerMessage(7, LogLevel.Debug, "Matched endpoint '{EndpointName}' is a fallback endpoint.", EventName = "FallbackMatch")] public static partial void FallbackMatch(ILogger logger, Endpoint endpointName); } } diff --git a/src/Http/Routing/src/RoutingMetrics.cs b/src/Http/Routing/src/RoutingMetrics.cs new file mode 100644 index 000000000000..a2561e97752f --- /dev/null +++ b/src/Http/Routing/src/RoutingMetrics.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.Metrics; +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Diagnostics.Metrics; + +namespace Microsoft.AspNetCore.Routing; + +internal sealed class RoutingMetrics +{ + public const string MeterName = "Microsoft.AspNetCore.Routing"; + + private readonly Meter _meter; + private readonly Counter _matchSuccessCounter; + private readonly Counter _matchFailureCounter; + + public RoutingMetrics(IMeterFactory meterFactory) + { + _meter = meterFactory.Create(MeterName); + + _matchSuccessCounter = _meter.CreateCounter( + "routing-match-success", + description: "Number of connections rejected by the server. Connections are rejected when the currently active count exceeds the value configured with MaxConcurrentConnections."); + + _matchFailureCounter = _meter.CreateCounter( + "routing-match-failure", + description: "Number of connections rejected by the server. Connections are rejected when the currently active count exceeds the value configured with MaxConcurrentConnections."); + } + + public bool MatchSuccessCounterEnabled => _matchSuccessCounter.Enabled; + + public void MatchSuccess(string route, bool isFallback) + { + _matchSuccessCounter.Add(1, + new KeyValuePair("route", route), + new KeyValuePair("fallback", isFallback)); + } + + public void MatchFailure() + { + _matchFailureCounter.Add(1); + } +} diff --git a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs index 5e6288e5caef..00982a2da430 100644 --- a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs +++ b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.TestObjects; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; @@ -90,7 +91,6 @@ public async Task Invoke_BackCompatGetRouteValue_ValueUsedFromEndpointFeature() { // Arrange var httpContext = CreateHttpContext(); - var middleware = CreateMiddleware(); // Act @@ -282,6 +282,7 @@ private EndpointRoutingMiddleware CreateMiddleware( logger ??= new Logger(NullLoggerFactory.Instance); matcherFactory ??= new TestMatcherFactory(true); listener ??= new DiagnosticListener("Microsoft.AspNetCore"); + var metrics = new RoutingMetrics(new TestMeterFactory()); var middleware = new EndpointRoutingMiddleware( matcherFactory, @@ -290,6 +291,7 @@ private EndpointRoutingMiddleware CreateMiddleware( new DefaultEndpointDataSource(), listener, Options.Create(new RouteOptions()), + metrics, next); return middleware; diff --git a/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj b/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj index be15210e4c0b..39ae97878631 100644 --- a/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj +++ b/src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj @@ -12,9 +12,12 @@ + + + diff --git a/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs b/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs new file mode 100644 index 000000000000..2b18083d9fba --- /dev/null +++ b/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs @@ -0,0 +1,146 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.Metrics; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.AspNetCore.Routing.TestObjects; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Diagnostics.Metrics; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; + +namespace Microsoft.AspNetCore.Routing; + +public class RoutingMetricsTests +{ + [Fact] + public async Task Match_Success() + { + // Arrange + var routeEndpointBuilder = new RouteEndpointBuilder(c => Task.CompletedTask, RoutePatternFactory.Parse("/{hi}"), order: 0); + var meterFactory = new TestMeterFactory(); + var middleware = CreateMiddleware( + matcherFactory: new TestMatcherFactory(true, c => + { + c.SetEndpoint(routeEndpointBuilder.Build()); + }), + meterFactory: meterFactory); + var httpContext = new DefaultHttpContext(); + var meter = meterFactory.Meters.Single(); + + using var routingMatchSuccessRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-success"); + using var routingMatchFailureRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-failure"); + + // Act + await middleware.Invoke(httpContext); + + // Assert + Assert.Equal(RoutingMetrics.MeterName, meter.Name); + Assert.Null(meter.Version); + + Assert.Collection(routingMatchSuccessRecorder.GetMeasurements(), + m => AssertSuccess(m, "/{hi}", fallback: false)); + Assert.Empty(routingMatchFailureRecorder.GetMeasurements()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Match_SuccessFallback_SetTagIfPresent(bool hasFallbackMetadata) + { + // Arrange + var routeEndpointBuilder = new RouteEndpointBuilder(c => Task.CompletedTask, RoutePatternFactory.Parse("/{hi}"), order: 0); + if (hasFallbackMetadata) + { + routeEndpointBuilder.Metadata.Add(FallbackMetadata.Instance); + } + var meterFactory = new TestMeterFactory(); + var middleware = CreateMiddleware( + matcherFactory: new TestMatcherFactory(true, c => + { + c.SetEndpoint(routeEndpointBuilder.Build()); + }), + meterFactory: meterFactory); + var httpContext = new DefaultHttpContext(); + var meter = meterFactory.Meters.Single(); + + using var routingMatchSuccessRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-success"); + using var routingMatchFailureRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-failure"); + + // Act + await middleware.Invoke(httpContext); + + // Assert + Assert.Equal(RoutingMetrics.MeterName, meter.Name); + Assert.Null(meter.Version); + + Assert.Collection(routingMatchSuccessRecorder.GetMeasurements(), + m => AssertSuccess(m, "/{hi}", fallback: hasFallbackMetadata)); + Assert.Empty(routingMatchFailureRecorder.GetMeasurements()); + } + + [Fact] + public async Task Match_Failure() + { + // Arrange + var meterFactory = new TestMeterFactory(); + var middleware = CreateMiddleware( + matcherFactory: new TestMatcherFactory(false), + meterFactory: meterFactory); + var httpContext = new DefaultHttpContext(); + var meter = meterFactory.Meters.Single(); + + using var routingMatchSuccessRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-success"); + using var routingMatchFailureRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-failure"); + + // Act + await middleware.Invoke(httpContext); + + // Assert + Assert.Equal(RoutingMetrics.MeterName, meter.Name); + Assert.Null(meter.Version); + + Assert.Empty(routingMatchSuccessRecorder.GetMeasurements()); + Assert.Collection(routingMatchFailureRecorder.GetMeasurements(), + m => Assert.Equal(1, m.Value)); + } + + private void AssertSuccess(Measurement measurement, string route, bool fallback) + { + Assert.Equal(1, measurement.Value); + Assert.Equal(route, (string)measurement.Tags.ToArray().Single(t => t.Key == "route").Value); + Assert.Equal(fallback, (bool)measurement.Tags.ToArray().Single(t => t.Key == "fallback").Value); + } + + private EndpointRoutingMiddleware CreateMiddleware( + ILogger logger = null, + MatcherFactory matcherFactory = null, + DiagnosticListener listener = null, + IMeterFactory meterFactory = null, + RequestDelegate next = null) + { + next ??= c => Task.CompletedTask; + logger ??= new Logger(NullLoggerFactory.Instance); + matcherFactory ??= new TestMatcherFactory(true); + listener ??= new DiagnosticListener("Microsoft.AspNetCore"); + var metrics = new RoutingMetrics(meterFactory ?? new TestMeterFactory()); + + var middleware = new EndpointRoutingMiddleware( + matcherFactory, + logger, + new DefaultEndpointRouteBuilder(Mock.Of()), + new DefaultEndpointDataSource(), + listener, + Options.Create(new RouteOptions()), + metrics, + next); + + return middleware; + } +} From 327983b2e3dc68da4415f249a50a192b1fb93d0c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 6 Jun 2023 16:26:27 +0800 Subject: [PATCH 2/9] Clean up --- src/Http/Routing/src/RoutingMetrics.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Routing/src/RoutingMetrics.cs b/src/Http/Routing/src/RoutingMetrics.cs index a2561e97752f..383ccaabfe2b 100644 --- a/src/Http/Routing/src/RoutingMetrics.cs +++ b/src/Http/Routing/src/RoutingMetrics.cs @@ -22,11 +22,11 @@ public RoutingMetrics(IMeterFactory meterFactory) _matchSuccessCounter = _meter.CreateCounter( "routing-match-success", - description: "Number of connections rejected by the server. Connections are rejected when the currently active count exceeds the value configured with MaxConcurrentConnections."); + description: "Number of requests successfully matched to an endpoint by routing."); _matchFailureCounter = _meter.CreateCounter( "routing-match-failure", - description: "Number of connections rejected by the server. Connections are rejected when the currently active count exceeds the value configured with MaxConcurrentConnections."); + description: "Number of requests that failed to match to an endpoint by routing."); } public bool MatchSuccessCounterEnabled => _matchSuccessCounter.Enabled; From 0b040cb76dd31f0280b71cab15663ccb0a33dca3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 6 Jun 2023 17:02:01 +0800 Subject: [PATCH 3/9] Update --- src/Http/Routing/src/RoutingMetrics.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Http/Routing/src/RoutingMetrics.cs b/src/Http/Routing/src/RoutingMetrics.cs index 383ccaabfe2b..9795868fa2ed 100644 --- a/src/Http/Routing/src/RoutingMetrics.cs +++ b/src/Http/Routing/src/RoutingMetrics.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Diagnostics.Metrics; -using System.Runtime.CompilerServices; using Microsoft.Extensions.Diagnostics.Metrics; namespace Microsoft.AspNetCore.Routing; From e820c6e2f8ec86cc331bff504c119cdd5e5af05b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 6 Jun 2023 17:32:38 +0800 Subject: [PATCH 4/9] Fix tests --- .../DependencyInjection/RoutingServiceCollectionExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs b/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs index 01d332757839..623e6e4a7781 100644 --- a/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs +++ b/src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs @@ -108,6 +108,7 @@ public static IServiceCollection AddRoutingCore(this IServiceCollection services // services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); // Set RouteHandlerOptions.ThrowOnBadRequest in development services.TryAddEnumerable(ServiceDescriptor.Transient, ConfigureRouteHandlerOptions>()); From d62640331cd11f0b7326b0004c8f56c871b7960c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 9 Jun 2023 13:03:06 +0800 Subject: [PATCH 5/9] Improve descriptions --- src/Http/Routing/src/RoutingMetrics.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Routing/src/RoutingMetrics.cs b/src/Http/Routing/src/RoutingMetrics.cs index 9795868fa2ed..8436bd050f9e 100644 --- a/src/Http/Routing/src/RoutingMetrics.cs +++ b/src/Http/Routing/src/RoutingMetrics.cs @@ -20,11 +20,11 @@ public RoutingMetrics(IMeterFactory meterFactory) _matchSuccessCounter = _meter.CreateCounter( "routing-match-success", - description: "Number of requests successfully matched to an endpoint by routing."); + description: "Number of requests that successfully matched to an endpoint."); _matchFailureCounter = _meter.CreateCounter( "routing-match-failure", - description: "Number of requests that failed to match to an endpoint by routing."); + description: "Number of requests that failed to match to an endpoint."); } public bool MatchSuccessCounterEnabled => _matchSuccessCounter.Enabled; From 47c95d378ec053f057afee735bad3139acc3fb61 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 9 Jun 2023 13:14:32 +0800 Subject: [PATCH 6/9] Test --- .../Routing/src/EndpointRoutingMiddleware.cs | 4 +-- .../test/UnitTests/RoutingMetricsTests.cs | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Http/Routing/src/EndpointRoutingMiddleware.cs b/src/Http/Routing/src/EndpointRoutingMiddleware.cs index 6238c067f2e9..fb42cf36959d 100644 --- a/src/Http/Routing/src/EndpointRoutingMiddleware.cs +++ b/src/Http/Routing/src/EndpointRoutingMiddleware.cs @@ -124,8 +124,8 @@ private Task SetRoutingAndContinue(HttpContext httpContext) } // It shouldn't be possible for a route to be matched via the route matcher and not have a route. - // Just in case, add a special (unknown) value as the route tag to metrics. - var route = endpoint.Metadata.GetMetadata()?.Route ?? "(unknown)"; + // Just in case, add a special (missing) value as the route tag to metrics. + var route = endpoint.Metadata.GetMetadata()?.Route ?? "(missing)"; _metrics.MatchSuccess(route, isFallback); } diff --git a/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs b/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs index 2b18083d9fba..6998afd595a0 100644 --- a/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs +++ b/src/Http/Routing/test/UnitTests/RoutingMetricsTests.cs @@ -85,6 +85,35 @@ public async Task Match_SuccessFallback_SetTagIfPresent(bool hasFallbackMetadata Assert.Empty(routingMatchFailureRecorder.GetMeasurements()); } + [Fact] + public async Task Match_Success_MissingRoute() + { + // Arrange + var meterFactory = new TestMeterFactory(); + var middleware = CreateMiddleware( + matcherFactory: new TestMatcherFactory(true, c => + { + c.SetEndpoint(new Endpoint(c => Task.CompletedTask, EndpointMetadataCollection.Empty, "Test name")); + }), + meterFactory: meterFactory); + var httpContext = new DefaultHttpContext(); + var meter = meterFactory.Meters.Single(); + + using var routingMatchSuccessRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-success"); + using var routingMatchFailureRecorder = new InstrumentRecorder(meterFactory, RoutingMetrics.MeterName, "routing-match-failure"); + + // Act + await middleware.Invoke(httpContext); + + // Assert + Assert.Equal(RoutingMetrics.MeterName, meter.Name); + Assert.Null(meter.Version); + + Assert.Collection(routingMatchSuccessRecorder.GetMeasurements(), + m => AssertSuccess(m, "(missing)", fallback: false)); + Assert.Empty(routingMatchFailureRecorder.GetMeasurements()); + } + [Fact] public async Task Match_Failure() { From 80cc820e752c86a8de3d48ecfecb0bc84d8b3bbe Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 9 Jun 2023 16:02:14 +0800 Subject: [PATCH 7/9] Fix tests --- .../Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs index bd352464d104..e8924d7733e6 100644 --- a/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.Metrics; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Moq; @@ -354,6 +355,7 @@ private IServiceProvider CreateServices(MatcherFactory matcherFactory) services.AddSingleton(matcherFactory); } + services.AddMetrics(); services.AddLogging(); services.AddOptions(); services.AddRouting(); From 94cc768bcbdc281418a46e0e21842e215ccdfd73 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 10 Jun 2023 12:35:24 +0800 Subject: [PATCH 8/9] Update description --- src/Http/Routing/src/RoutingMetrics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Routing/src/RoutingMetrics.cs b/src/Http/Routing/src/RoutingMetrics.cs index 8436bd050f9e..288ae5d8f011 100644 --- a/src/Http/Routing/src/RoutingMetrics.cs +++ b/src/Http/Routing/src/RoutingMetrics.cs @@ -24,7 +24,7 @@ public RoutingMetrics(IMeterFactory meterFactory) _matchFailureCounter = _meter.CreateCounter( "routing-match-failure", - description: "Number of requests that failed to match to an endpoint."); + description: "Number of requests that failed to match to an endpoint. An unmatched request may be handled by later middleware, such as the static files or authentication middleware."); } public bool MatchSuccessCounterEnabled => _matchSuccessCounter.Enabled; From d7765a91f363746f95e4d9cd3bd58f1ddedde3ec Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 10 Jun 2023 14:02:20 +0800 Subject: [PATCH 9/9] Fix build --- src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj | 1 + src/Mvc/Mvc/test/Microsoft.AspNetCore.Mvc.Test.csproj | 1 + src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj index 3e51be1d5020..cb761dd0df34 100644 --- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj +++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj @@ -42,6 +42,7 @@ + diff --git a/src/Mvc/Mvc/test/Microsoft.AspNetCore.Mvc.Test.csproj b/src/Mvc/Mvc/test/Microsoft.AspNetCore.Mvc.Test.csproj index 2b13fa1d451b..fbd6966f85f2 100644 --- a/src/Mvc/Mvc/test/Microsoft.AspNetCore.Mvc.Test.csproj +++ b/src/Mvc/Mvc/test/Microsoft.AspNetCore.Mvc.Test.csproj @@ -13,5 +13,6 @@ + diff --git a/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs b/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs index 15a46705ed8d..9b6f9062d347 100644 --- a/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs +++ b/src/Mvc/Mvc/test/MvcServiceCollectionExtensionsTest.cs @@ -25,6 +25,7 @@ using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.Metrics; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Moq; @@ -382,6 +383,7 @@ public void AddMvc_NoScopedServiceIsReferredToByASingleton() services.AddSingleton(diagnosticListener); services.AddSingleton(diagnosticListener); services.AddSingleton(new ConfigurationBuilder().Build()); + services.AddMetrics(); services.AddLogging(); services.AddOptions(); services.AddMvc();