Skip to content

Commit 34d37ec

Browse files
authored
Add routing metrics (#48637)
1 parent 188d4b0 commit 34d37ec

12 files changed

+258
-9
lines changed

src/Http/Routing/perf/Microbenchmarks/EndpointRoutingShortCircuitBenchmark.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Http;
88
using Microsoft.AspNetCore.Routing.Matching;
99
using Microsoft.AspNetCore.Routing.ShortCircuit;
10+
using Microsoft.AspNetCore.Testing;
1011
using Microsoft.Extensions.Logging.Abstractions;
1112
using Microsoft.Extensions.Options;
1213
using Microsoft.Extensions.Primitives;
@@ -21,6 +22,7 @@ public class EndpointRoutingShortCircuitBenchmark
2122
[GlobalSetup]
2223
public void Setup()
2324
{
25+
var routingMetrics = new RoutingMetrics(new TestMeterFactory());
2426
var normalEndpoint = new Endpoint(context => Task.CompletedTask, new EndpointMetadataCollection(), "normal");
2527

2628
_normalEndpointMiddleware = new EndpointRoutingMiddleware(
@@ -30,6 +32,7 @@ public void Setup()
3032
new BenchmarkEndpointDataSource(),
3133
new DiagnosticListener("benchmark"),
3234
Options.Create(new RouteOptions()),
35+
routingMetrics,
3336
context => Task.CompletedTask);
3437

3538
var shortCircuitEndpoint = new Endpoint(context => Task.CompletedTask, new EndpointMetadataCollection(new ShortCircuitMetadata(200)), "shortcircuit");
@@ -41,8 +44,8 @@ public void Setup()
4144
new BenchmarkEndpointDataSource(),
4245
new DiagnosticListener("benchmark"),
4346
Options.Create(new RouteOptions()),
47+
routingMetrics,
4448
context => Task.CompletedTask);
45-
4649
}
4750

4851
[Benchmark]

src/Http/Routing/perf/Microbenchmarks/Microsoft.AspNetCore.Routing.Microbenchmarks.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -31,6 +31,7 @@
3131
<Compile Include="..\..\test\UnitTests\Matching\TreeRouterMatcherBuilder.cs">
3232
<Link>Matching\TreeRouterMatcherBuilder.cs</Link>
3333
</Compile>
34+
<Compile Include="$(SharedSourceRoot)Metrics\TestMeterFactory.cs" LinkBase="shared" />
3435
<Compile Include="..\..\test\UnitTests\TestObjects\TestServiceProvider.cs" Link="Matching\TestServiceProvider.cs" />
3536
</ItemGroup>
3637

@@ -39,6 +40,7 @@
3940
<Reference Include="Microsoft.AspNetCore.Http" />
4041
<Reference Include="Microsoft.AspNetCore.Routing" />
4142
<Reference Include="Microsoft.Extensions.DependencyInjection" />
43+
<Reference Include="Microsoft.Extensions.Diagnostics" />
4244
<Reference Include="Microsoft.Extensions.Logging" />
4345

4446
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />

src/Http/Routing/src/DependencyInjection/RoutingServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ public static IServiceCollection AddRoutingCore(this IServiceCollection services
108108
//
109109
services.TryAddSingleton<TemplateBinderFactory, DefaultTemplateBinderFactory>();
110110
services.TryAddSingleton<RoutePatternTransformer, DefaultRoutePatternTransformer>();
111+
services.TryAddSingleton<RoutingMetrics>();
111112

112113
// Set RouteHandlerOptions.ThrowOnBadRequest in development
113114
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<RouteHandlerOptions>, ConfigureRouteHandlerOptions>());

src/Http/Routing/src/EndpointRoutingMiddleware.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Authorization;
88
using Microsoft.AspNetCore.Cors.Infrastructure;
99
using Microsoft.AspNetCore.Http;
10+
using Microsoft.AspNetCore.Http.Metadata;
1011
using Microsoft.AspNetCore.Routing.Matching;
1112
using Microsoft.AspNetCore.Routing.ShortCircuit;
1213
using Microsoft.Extensions.Logging;
@@ -22,6 +23,7 @@ internal sealed partial class EndpointRoutingMiddleware
2223
private readonly ILogger _logger;
2324
private readonly EndpointDataSource _endpointDataSource;
2425
private readonly DiagnosticListener _diagnosticListener;
26+
private readonly RoutingMetrics _metrics;
2527
private readonly RequestDelegate _next;
2628
private readonly RouteOptions _routeOptions;
2729
private Task<Matcher>? _initializationTask;
@@ -33,13 +35,15 @@ public EndpointRoutingMiddleware(
3335
EndpointDataSource rootCompositeEndpointDataSource,
3436
DiagnosticListener diagnosticListener,
3537
IOptions<RouteOptions> routeOptions,
38+
RoutingMetrics metrics,
3639
RequestDelegate next)
3740
{
3841
ArgumentNullException.ThrowIfNull(endpointRouteBuilder);
3942

4043
_matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
4144
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
4245
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
46+
_metrics = metrics;
4347
_next = next ?? throw new ArgumentNullException(nameof(next));
4448
_routeOptions = routeOptions.Value;
4549

@@ -98,6 +102,7 @@ private Task SetRoutingAndContinue(HttpContext httpContext)
98102
if (endpoint == null)
99103
{
100104
Log.MatchFailure(_logger);
105+
_metrics.MatchFailure();
101106
}
102107
else
103108
{
@@ -107,12 +112,21 @@ private Task SetRoutingAndContinue(HttpContext httpContext)
107112
Write(_diagnosticListener, httpContext);
108113
}
109114

110-
Log.MatchSuccess(_logger, endpoint);
111-
112-
if (_logger.IsEnabled(LogLevel.Debug)
113-
&& endpoint.Metadata.GetMetadata<FallbackMetadata>() is not null)
115+
if (_logger.IsEnabled(LogLevel.Debug) || _metrics.MatchSuccessCounterEnabled)
114116
{
115-
Log.FallbackMatch(_logger, endpoint);
117+
var isFallback = endpoint.Metadata.GetMetadata<FallbackMetadata>() is not null;
118+
119+
Log.MatchSuccess(_logger, endpoint);
120+
121+
if (isFallback)
122+
{
123+
Log.FallbackMatch(_logger, endpoint);
124+
}
125+
126+
// It shouldn't be possible for a route to be matched via the route matcher and not have a route.
127+
// Just in case, add a special (missing) value as the route tag to metrics.
128+
var route = endpoint.Metadata.GetMetadata<IRouteDiagnosticsMetadata>()?.Route ?? "(missing)";
129+
_metrics.MatchSuccess(route, isFallback);
116130
}
117131

118132
var shortCircuitMetadata = endpoint.Metadata.GetMetadata<ShortCircuitMetadata>();
@@ -290,7 +304,7 @@ public static void MatchSkipped(ILogger logger, Endpoint endpoint)
290304
[LoggerMessage(6, LogLevel.Information, "The endpoint '{EndpointName}' is being short circuited without running additional middleware or producing a response.", EventName = "ShortCircuitedEndpoint")]
291305
public static partial void ShortCircuitedEndpoint(ILogger logger, Endpoint endpointName);
292306

293-
[LoggerMessage(7, LogLevel.Debug, "Matched endpoint '{EndpointName}' is a fallback endpoint.", EventName = "FallbackMatch", SkipEnabledCheck = true)]
307+
[LoggerMessage(7, LogLevel.Debug, "Matched endpoint '{EndpointName}' is a fallback endpoint.", EventName = "FallbackMatch")]
294308
public static partial void FallbackMatch(ILogger logger, Endpoint endpointName);
295309
}
296310
}

src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<Reference Include="Microsoft.AspNetCore.Authorization" />
4343
<Reference Include="Microsoft.AspNetCore.Http.Extensions" />
4444
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
45+
<Reference Include="Microsoft.Extensions.Diagnostics.Abstractions" />
4546
<Reference Include="Microsoft.Extensions.ObjectPool" />
4647
<Reference Include="Microsoft.Extensions.Options" />
4748
</ItemGroup>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics.Metrics;
5+
using Microsoft.Extensions.Diagnostics.Metrics;
6+
7+
namespace Microsoft.AspNetCore.Routing;
8+
9+
internal sealed class RoutingMetrics
10+
{
11+
public const string MeterName = "Microsoft.AspNetCore.Routing";
12+
13+
private readonly Meter _meter;
14+
private readonly Counter<long> _matchSuccessCounter;
15+
private readonly Counter<long> _matchFailureCounter;
16+
17+
public RoutingMetrics(IMeterFactory meterFactory)
18+
{
19+
_meter = meterFactory.Create(MeterName);
20+
21+
_matchSuccessCounter = _meter.CreateCounter<long>(
22+
"routing-match-success",
23+
description: "Number of requests that successfully matched to an endpoint.");
24+
25+
_matchFailureCounter = _meter.CreateCounter<long>(
26+
"routing-match-failure",
27+
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.");
28+
}
29+
30+
public bool MatchSuccessCounterEnabled => _matchSuccessCounter.Enabled;
31+
32+
public void MatchSuccess(string route, bool isFallback)
33+
{
34+
_matchSuccessCounter.Add(1,
35+
new KeyValuePair<string, object?>("route", route),
36+
new KeyValuePair<string, object?>("fallback", isFallback));
37+
}
38+
39+
public void MatchFailure()
40+
{
41+
_matchFailureCounter.Add(1);
42+
}
43+
}

src/Http/Routing/test/UnitTests/Builder/EndpointRoutingApplicationBuilderExtensionsTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNetCore.Routing.Patterns;
1010
using Microsoft.AspNetCore.Routing.TestObjects;
1111
using Microsoft.Extensions.DependencyInjection;
12+
using Microsoft.Extensions.Diagnostics.Metrics;
1213
using Microsoft.Extensions.Hosting;
1314
using Microsoft.Extensions.Options;
1415
using Moq;
@@ -354,6 +355,7 @@ private IServiceProvider CreateServices(MatcherFactory matcherFactory)
354355
services.AddSingleton<MatcherFactory>(matcherFactory);
355356
}
356357

358+
services.AddMetrics();
357359
services.AddLogging();
358360
services.AddOptions();
359361
services.AddRouting();

src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Http.Features;
88
using Microsoft.AspNetCore.Routing.Matching;
99
using Microsoft.AspNetCore.Routing.TestObjects;
10+
using Microsoft.AspNetCore.Testing;
1011
using Microsoft.Extensions.Logging;
1112
using Microsoft.Extensions.Logging.Abstractions;
1213
using Microsoft.Extensions.Logging.Testing;
@@ -90,7 +91,6 @@ public async Task Invoke_BackCompatGetRouteValue_ValueUsedFromEndpointFeature()
9091
{
9192
// Arrange
9293
var httpContext = CreateHttpContext();
93-
9494
var middleware = CreateMiddleware();
9595

9696
// Act
@@ -282,6 +282,7 @@ private EndpointRoutingMiddleware CreateMiddleware(
282282
logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
283283
matcherFactory ??= new TestMatcherFactory(true);
284284
listener ??= new DiagnosticListener("Microsoft.AspNetCore");
285+
var metrics = new RoutingMetrics(new TestMeterFactory());
285286

286287
var middleware = new EndpointRoutingMiddleware(
287288
matcherFactory,
@@ -290,6 +291,7 @@ private EndpointRoutingMiddleware CreateMiddleware(
290291
new DefaultEndpointDataSource(),
291292
listener,
292293
Options.Create(new RouteOptions()),
294+
metrics,
293295
next);
294296

295297
return middleware;

src/Http/Routing/test/UnitTests/Microsoft.AspNetCore.Routing.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
<Reference Include="Microsoft.AspNetCore.Routing" />
1313
<Reference Include="Microsoft.AspNetCore.Http.Results" />
1414
<Reference Include="Microsoft.Extensions.DependencyInjection" />
15+
<Reference Include="Microsoft.Extensions.Diagnostics" />
1516
<Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
1617
<Reference Include="Microsoft.Extensions.Logging" />
1718
<Reference Include="Microsoft.Extensions.WebEncoders" />
19+
20+
<Compile Include="$(SharedSourceRoot)Metrics\TestMeterFactory.cs" LinkBase="shared" />
1821
</ItemGroup>
1922

2023
</Project>

0 commit comments

Comments
 (0)