Added benchmarks for link generation
This commit is contained in:
parent
731767837a
commit
0986f74264
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{
|
||||
public abstract class EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private protected MatcherEndpoint[] Endpoints;
|
||||
private protected HttpContext[] Requests;
|
||||
|
||||
private protected void SetupEndpoints(params MatcherEndpoint[] endpoints)
|
||||
{
|
||||
Endpoints = endpoints;
|
||||
}
|
||||
|
||||
// The older routing implementations retrieve services when they first execute.
|
||||
private protected IServiceProvider CreateServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
|
||||
services.AddLogging();
|
||||
services.AddOptions();
|
||||
services.AddRouting();
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Singleton<EndpointDataSource>(new DefaultEndpointDataSource(Endpoints)));
|
||||
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private protected DfaMatcherBuilder CreateDfaMatcherBuilder()
|
||||
{
|
||||
return CreateServices().GetRequiredService<DfaMatcherBuilder>();
|
||||
}
|
||||
|
||||
private protected static int[] SampleRequests(int endpointCount, int count)
|
||||
{
|
||||
// This isn't very high tech, but it's at least regular distribution.
|
||||
// We sort the route templates by precedence, so this should result in
|
||||
// an even distribution of the 'complexity' of the routes that are exercised.
|
||||
var frequency = endpointCount / count;
|
||||
if (frequency < 2)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The sample count is too high. This won't produce an accurate sampling" +
|
||||
"of the request data.");
|
||||
}
|
||||
|
||||
var samples = new int[count];
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
samples[i] = i * frequency;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private protected void Validate(HttpContext httpContext, Endpoint expected, Endpoint actual)
|
||||
{
|
||||
if (!object.ReferenceEquals(expected, actual))
|
||||
{
|
||||
var message = new StringBuilder();
|
||||
message.AppendLine($"Validation failed for request {Array.IndexOf(Requests, httpContext)}");
|
||||
message.AppendLine($"{httpContext.Request.Method} {httpContext.Request.Path}");
|
||||
message.AppendLine($"expected: '{((MatcherEndpoint)expected)?.DisplayName ?? "null"}'");
|
||||
message.AppendLine($"actual: '{((MatcherEndpoint)actual)?.DisplayName ?? "null"}'");
|
||||
throw new InvalidOperationException(message.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
protected void AssertUrl(string expectedUrl, string actualUrl)
|
||||
{
|
||||
AssertUrl(expectedUrl, actualUrl, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
protected void AssertUrl(string expectedUrl, string actualUrl, StringComparison stringComparison)
|
||||
{
|
||||
if (!string.Equals(expectedUrl, actualUrl, stringComparison))
|
||||
{
|
||||
throw new InvalidOperationException($"Expected: {expectedUrl}, Actual: {actualUrl}");
|
||||
}
|
||||
}
|
||||
|
||||
protected MatcherEndpoint CreateEndpoint(
|
||||
string template,
|
||||
object defaults = null,
|
||||
object constraints = null,
|
||||
object requiredValues = null,
|
||||
int order = 0,
|
||||
string displayName = null,
|
||||
string routeName = null,
|
||||
params object[] metadata)
|
||||
{
|
||||
var endpointMetadata = new List<object>(metadata ?? Array.Empty<object>());
|
||||
endpointMetadata.Add(new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues)));
|
||||
|
||||
return new MatcherEndpoint(
|
||||
MatcherEndpoint.EmptyInvoker,
|
||||
RoutePatternFactory.Parse(template, defaults, constraints),
|
||||
order,
|
||||
new EndpointMetadataCollection(endpointMetadata),
|
||||
displayName);
|
||||
}
|
||||
|
||||
protected (HttpContext httpContext, RouteValueDictionary ambientValues) CreateCurrentRequestContext(
|
||||
object ambientValues = null)
|
||||
{
|
||||
var feature = new EndpointFeature { Values = new RouteValueDictionary(ambientValues) };
|
||||
var context = new DefaultHttpContext();
|
||||
context.Features.Set<IEndpointFeature>(feature);
|
||||
|
||||
return (context, feature.Values);
|
||||
}
|
||||
|
||||
protected void CreateOutboundRouteEntry(TreeRouteBuilder treeRouteBuilder, MatcherEndpoint endpoint)
|
||||
{
|
||||
var routeValuesAddressMetadata = endpoint.Metadata.GetMetadata<IRouteValuesAddressMetadata>();
|
||||
var requiredValues = routeValuesAddressMetadata?.RequiredValues ?? new RouteValueDictionary();
|
||||
|
||||
treeRouteBuilder.MapOutbound(
|
||||
NullRouter.Instance,
|
||||
new RouteTemplate(RoutePatternFactory.Parse(
|
||||
endpoint.RoutePattern.RawText,
|
||||
defaults: endpoint.RoutePattern.Defaults,
|
||||
constraints: null)),
|
||||
requiredLinkValues: new RouteValueDictionary(requiredValues),
|
||||
routeName: null,
|
||||
order: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public partial class LinkGenerationGithubBenchmark
|
||||
{
|
||||
private LinkGenerator _linkGenerator;
|
||||
private TreeRouter _treeRouter;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
private RouteValueDictionary _lookUpValues;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
foreach (var endpoint in Endpoints)
|
||||
{
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, endpoint);
|
||||
}
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
|
||||
// Get the endpoint to test and pre-populate the lookup values with the defaults
|
||||
// (as they are dynamically generated) and update with other required parameter values.
|
||||
// /repos/{owner}/{repo}/issues/comments/{commentId}
|
||||
var endpointToTest = Endpoints[176];
|
||||
_lookUpValues = new RouteValueDictionary(endpointToTest.RoutePattern.Defaults);
|
||||
_lookUpValues["owner"] = "aspnet";
|
||||
_lookUpValues["repo"] = "routing";
|
||||
_lookUpValues["commentId"] = "20202";
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(_lookUpValues)));
|
||||
|
||||
AssertUrl("/repos/aspnet/routing/issues/comments/20202", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(_lookUpValues));
|
||||
|
||||
AssertUrl("/repos/aspnet/routing/issues/comments/20202", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteRouteValuesBasedEndpointFinderBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private IEndpointFinder<RouteValuesAddress> _finder;
|
||||
private TestEndpointFinder _baseFinder;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Products/Details";
|
||||
var defaults = new { controller = "Products", action = "Details" };
|
||||
var requiredValues = new { controller = "Products", action = "Details" };
|
||||
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues, routeName: "ProductDetails"));
|
||||
var services = CreateServices();
|
||||
_finder = services.GetRequiredService<IEndpointFinder<RouteValuesAddress>>();
|
||||
_baseFinder = new TestEndpointFinder(Endpoints[0]);
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Baseline()
|
||||
{
|
||||
var actual = _baseFinder.FindEndpoints(address: 0);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void RouteValues()
|
||||
{
|
||||
var actual = _finder.FindEndpoints(new RouteValuesAddress
|
||||
{
|
||||
AmbientValues = _requestContext.AmbientValues,
|
||||
ExplicitValues = new RouteValueDictionary(new { controller = "Products", action = "Details" }),
|
||||
RouteName = null
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void RouteName()
|
||||
{
|
||||
var actual = _finder.FindEndpoints(new RouteValuesAddress
|
||||
{
|
||||
AmbientValues = _requestContext.AmbientValues,
|
||||
RouteName = "ProductDetails"
|
||||
});
|
||||
}
|
||||
|
||||
private class TestEndpointFinder : IEndpointFinder<int>
|
||||
{
|
||||
private readonly Endpoint _endpoint;
|
||||
|
||||
public TestEndpointFinder(Endpoint endpoint)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
|
||||
public IEnumerable<Endpoint> FindEndpoints(int address)
|
||||
{
|
||||
return new[] { _endpoint };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteWithConstraintsBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private TreeRouter _treeRouter;
|
||||
private LinkGenerator _linkGenerator;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Customers/Details/{category}/{region}/{id:int}";
|
||||
var defaults = new { controller = "Customers", action = "Details" };
|
||||
var requiredValues = new { controller = "Customers", action = "Details" };
|
||||
|
||||
// Endpoint routing related
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
})));
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
}));
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteWithNoParametersBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private TreeRouter _treeRouter;
|
||||
private LinkGenerator _linkGenerator;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Products/Details";
|
||||
var defaults = new { controller = "Products", action = "Details" };
|
||||
var requiredValues = new { controller = "Products", action = "Details" };
|
||||
|
||||
// Endpoint routing related
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Products",
|
||||
action = "Details",
|
||||
})));
|
||||
|
||||
AssertUrl("/Products/Details", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Products",
|
||||
action = "Details",
|
||||
}));
|
||||
|
||||
AssertUrl("/Products/Details", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Tree;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.LinkGeneration
|
||||
{
|
||||
public class SingleRouteWithParametersBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private TreeRouter _treeRouter;
|
||||
private LinkGenerator _linkGenerator;
|
||||
private (HttpContext HttpContext, RouteValueDictionary AmbientValues) _requestContext;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var template = "Customers/Details/{category}/{region}/{id}";
|
||||
var defaults = new { controller = "Customers", action = "Details" };
|
||||
var requiredValues = new { controller = "Customers", action = "Details" };
|
||||
|
||||
// Endpoint routing related
|
||||
SetupEndpoints(CreateEndpoint(template, defaults, requiredValues: requiredValues));
|
||||
var services = CreateServices();
|
||||
_linkGenerator = services.GetRequiredService<LinkGenerator>();
|
||||
|
||||
// Attribute routing related
|
||||
var treeRouteBuilder = services.GetRequiredService<TreeRouteBuilder>();
|
||||
CreateOutboundRouteEntry(treeRouteBuilder, Endpoints[0]);
|
||||
_treeRouter = treeRouteBuilder.Build();
|
||||
|
||||
_requestContext = CreateCurrentRequestContext();
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void TreeRouter()
|
||||
{
|
||||
var virtualPathData = _treeRouter.GetVirtualPath(new VirtualPathContext(
|
||||
_requestContext.HttpContext,
|
||||
ambientValues: _requestContext.AmbientValues,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
})));
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", virtualPathData?.VirtualPath);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void EndpointRouting()
|
||||
{
|
||||
var actualUrl = _linkGenerator.GetLink(
|
||||
_requestContext.HttpContext,
|
||||
values: new RouteValueDictionary(
|
||||
new
|
||||
{
|
||||
controller = "Customers",
|
||||
action = "Details",
|
||||
category = "Administration",
|
||||
region = "US",
|
||||
id = 10
|
||||
}));
|
||||
|
||||
AssertUrl("/Customers/Details/Administration/US/10", actualUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using BenchmarkDotNet.Attributes;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/Azure/azure-rest-api-specs
|
||||
public partial class MatcherAzureBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherAzureBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int SampleCount = 100;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// This code was generated by the Swaggatherer
|
||||
public partial class MatcherAzureBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherAzureBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int EndpointCount = 5160;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public abstract class MatcherBenchmarkBase
|
||||
{
|
||||
private protected MatcherEndpoint[] Endpoints;
|
||||
private protected HttpContext[] Requests;
|
||||
|
||||
// The older routing implementations retrieve services when they first execute.
|
||||
private protected static IServiceProvider CreateServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddOptions();
|
||||
services.AddRouting();
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private protected DfaMatcherBuilder CreateDfaMatcherBuilder()
|
||||
{
|
||||
return CreateServices().GetRequiredService<DfaMatcherBuilder>();
|
||||
}
|
||||
|
||||
private protected static MatcherEndpoint CreateEndpoint(string template, string httpMethod = null)
|
||||
{
|
||||
var metadata = new List<object>();
|
||||
if (httpMethod != null)
|
||||
{
|
||||
metadata.Add(new HttpMethodMetadata(new string[] { httpMethod, }));
|
||||
}
|
||||
|
||||
return new MatcherEndpoint(
|
||||
MatcherEndpoint.EmptyInvoker,
|
||||
RoutePatternFactory.Parse(template),
|
||||
0,
|
||||
new EndpointMetadataCollection(metadata),
|
||||
template);
|
||||
}
|
||||
|
||||
private protected static int[] SampleRequests(int endpointCount, int count)
|
||||
{
|
||||
// This isn't very high tech, but it's at least regular distribution.
|
||||
// We sort the route templates by precedence, so this should result in
|
||||
// an even distribution of the 'complexity' of the routes that are exercised.
|
||||
var frequency = endpointCount / count;
|
||||
if (frequency < 2)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The sample count is too high. This won't produce an accurate sampling" +
|
||||
"of the request data.");
|
||||
}
|
||||
|
||||
var samples = new int[count];
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
samples[i] = i * frequency;
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private protected void Validate(HttpContext httpContext, Endpoint expected, Endpoint actual)
|
||||
{
|
||||
if (!object.ReferenceEquals(expected, actual))
|
||||
{
|
||||
var message = new StringBuilder();
|
||||
message.AppendLine($"Validation failed for request {Array.IndexOf(Requests, httpContext)}");
|
||||
message.AppendLine($"{httpContext.Request.Method} {httpContext.Request.Path}");
|
||||
message.AppendLine($"expected: '{((MatcherEndpoint)expected)?.DisplayName ?? "null"}'");
|
||||
message.AppendLine($"actual: '{((MatcherEndpoint)actual)?.DisplayName ?? "null"}'");
|
||||
throw new InvalidOperationException(message.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using BenchmarkDotNet.Attributes;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/Azure/azure-rest-api-specs
|
||||
public partial class MatcherFindCandidateSetAzureBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherFindCandidateSetAzureBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
// SegmentCount should be max-segments + 1, but we don't have a good way to compute
|
||||
// it here, so using 16 as a safe guess.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// This code was generated by the Swaggatherer
|
||||
public partial class MatcherFindCandidateSetAzureBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherFindCandidateSetAzureBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int EndpointCount = 3517;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public partial class MatcherFindCandidateSetGithubBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherFindCandidateSetGithubBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
// SegmentCount should be max-segments + 1, but we don't have a good way to compute
|
||||
// it here, so using 16 as a safe guess.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// This code was generated by the Swaggatherer
|
||||
public partial class MatcherFindCandidateSetGithubBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherFindCandidateSetGithubBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int EndpointCount = 155;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http;
|
|||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class MatcherFindCandidateSetSingleEntryBenchmark : MatcherBenchmarkBase
|
||||
public class MatcherFindCandidateSetSingleEntryBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
// SegmentCount should be max-segments + 1
|
||||
private const int SegmentCount = 2;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http;
|
|||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class MatcherFindCandidateSetSmallEntryCountBenchmark : MatcherBenchmarkBase
|
||||
public class MatcherFindCandidateSetSmallEntryCountBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
// SegmentCount should be max-segments + 1
|
||||
private const int SegmentCount = 6;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public partial class MatcherGithubBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherGithubBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private BarebonesMatcher _baseline;
|
||||
private Matcher _dfa;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// This code was generated by the Swaggatherer
|
||||
public partial class MatcherGithubBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherGithubBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int EndpointCount = 243;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Just like TechEmpower Plaintext
|
||||
public partial class MatcherSingleEntryBenchmark : MatcherBenchmarkBase
|
||||
public partial class MatcherSingleEntryBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int SampleCount = 100;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,51 +10,81 @@ namespace Swaggatherer
|
|||
{
|
||||
public static string Execute(IReadOnlyList<RouteEntry> entries)
|
||||
{
|
||||
var controllerCount = 0;
|
||||
var templatesVisited = new Dictionary<string, (int ControllerIndex, int ActionIndex)>(
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var setupEndpointsLines = new List<string>();
|
||||
for (var i = 0; i < entries.Count; i++)
|
||||
{
|
||||
{
|
||||
var entry = entries[i];
|
||||
var httpMethodText = entry.Method == null ? string.Empty : $", \"{entry.Method.ToUpperInvariant()}\"";
|
||||
setupEndpointsLines.Add($" _endpoints[{i}] = CreateEndpoint(\"{entry.Template.TemplateText}\"{httpMethodText});");
|
||||
|
||||
// In attribute routing, same template is used for all actions within that controller. The following
|
||||
// simulates that where we only increment the controller count when a new endpoint for a new template
|
||||
// is being created.
|
||||
var template = entry.Template.TemplateText;
|
||||
if (!templatesVisited.TryGetValue(template, out var visitedTemplateInfo))
|
||||
{
|
||||
controllerCount++;
|
||||
visitedTemplateInfo = (controllerCount, 0);
|
||||
}
|
||||
|
||||
// Increment the action count within a controller template
|
||||
visitedTemplateInfo.ActionIndex++;
|
||||
templatesVisited[template] = visitedTemplateInfo;
|
||||
|
||||
var controllerName = $"Controller{visitedTemplateInfo.ControllerIndex}";
|
||||
var actionName = $"Action{visitedTemplateInfo.ActionIndex}";
|
||||
|
||||
var httpMethodText = entry.Method == null ? "httpMethod: null" : $"\"{entry.Method.ToUpperInvariant()}\"";
|
||||
setupEndpointsLines.Add($" Endpoints[{i}] = CreateEndpoint(\"{template}\", \"{controllerName}\", \"{actionName}\", {httpMethodText});");
|
||||
}
|
||||
|
||||
var setupRequestsLines = new List<string>();
|
||||
for (var i = 0; i < entries.Count; i++)
|
||||
{
|
||||
setupRequestsLines.Add($" _requests[{i}] = new DefaultHttpContext();");
|
||||
setupRequestsLines.Add($" _requests[{i}].RequestServices = CreateServices();");
|
||||
setupRequestsLines.Add($" _requests[{i}].Request.Method = \"{entries[i].Method.ToUpperInvariant()}\";");
|
||||
setupRequestsLines.Add($" _requests[{i}].Request.Path = \"{entries[i].RequestUrl}\";");
|
||||
var entry = entries[i];
|
||||
setupRequestsLines.Add($" Requests[{i}] = new DefaultHttpContext();");
|
||||
setupRequestsLines.Add($" Requests[{i}].RequestServices = CreateServices();");
|
||||
|
||||
if (entry.Method != null)
|
||||
{
|
||||
setupRequestsLines.Add($" Requests[{i}].Request.Method = \"{entries[i].Method.ToUpperInvariant()}\";");
|
||||
}
|
||||
|
||||
setupRequestsLines.Add($" Requests[{i}].Request.Path = \"{entries[i].RequestUrl}\";");
|
||||
}
|
||||
|
||||
var setupMatcherLines = new List<string>();
|
||||
for (var i = 0; i < entries.Count; i++)
|
||||
{
|
||||
setupMatcherLines.Add($" builder.AddEndpoint(_endpoints[{i}]);");
|
||||
setupMatcherLines.Add($" builder.AddEndpoint(Endpoints[{i}]);");
|
||||
}
|
||||
|
||||
return string.Format(@"
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
namespace Microsoft.AspNetCore.Routing
|
||||
{{
|
||||
// This code was generated by the Swaggatherer
|
||||
public partial class GeneratedBenchmark : MatcherBenchmarkBase
|
||||
public partial class GeneratedBenchmark : EndpointRoutingBenchmarkBase
|
||||
{{
|
||||
private const int EndpointCount = {3};
|
||||
|
||||
private void SetupEndpoints()
|
||||
{{
|
||||
_endpoints = new MatcherEndpoint[{3}];
|
||||
Endpoints = new MatcherEndpoint[{3}];
|
||||
{0}
|
||||
}}
|
||||
|
||||
private void SetupRequests()
|
||||
{{
|
||||
_requests = new HttpContext[{3}];
|
||||
Requests = new HttpContext[{3}];
|
||||
{1}
|
||||
}}
|
||||
|
||||
|
|
@ -63,8 +93,39 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{2}
|
||||
return builder.Build();
|
||||
}}
|
||||
|
||||
private MatcherEndpoint CreateEndpoint(string template, string controllerName, string actionName, string httpMethod)
|
||||
{{
|
||||
var requiredValues = new
|
||||
{{
|
||||
area = (string)null,
|
||||
controller = controllerName,
|
||||
action = actionName,
|
||||
page = (string)null
|
||||
}};
|
||||
var defaults = new
|
||||
{{
|
||||
area = (string)null,
|
||||
controller = controllerName,
|
||||
action = actionName,
|
||||
page = (string)null
|
||||
}};
|
||||
|
||||
var metadata = new List<object>();
|
||||
if (httpMethod != null)
|
||||
{{
|
||||
metadata.Add(new HttpMethodMetadata(new string[] {{ httpMethod }}));
|
||||
}}
|
||||
|
||||
return CreateEndpoint(
|
||||
template,
|
||||
defaults: defaults,
|
||||
requiredValues: requiredValues,
|
||||
metadata: metadata,
|
||||
routeName: controllerName);
|
||||
}}
|
||||
}}
|
||||
}}",
|
||||
}}",
|
||||
string.Join(Environment.NewLine, setupEndpointsLines),
|
||||
string.Join(Environment.NewLine, setupRequestsLines),
|
||||
string.Join(Environment.NewLine, setupMatcherLines),
|
||||
|
|
|
|||
Loading…
Reference in New Issue