From 9bbb6364f2b73afac04e724d69bcc14d39c13a05 Mon Sep 17 00:00:00 2001 From: Phil Proctor Date: Wed, 26 Dec 2018 15:05:20 -0500 Subject: [PATCH] Checkin for caching the template matching for significant route finder performance improvements (#728) --- .../Finder/DownstreamRouteFinder.cs | 2 +- .../IUrlPathToUrlTemplateMatcher.cs | 17 +-- .../UrlMatcher/RegExUrlMatcher.cs | 47 ++++---- src/Ocelot/Values/UpstreamPathTemplate.cs | 49 +++++---- ...lPathToUrlPathTemplateMatcherBenchmarks.cs | 103 +++++++++--------- .../DownstreamRouteFinderTests.cs | 17 +-- .../UrlMatcher/RegExUrlMatcherTests.cs | 3 +- 7 files changed, 124 insertions(+), 114 deletions(-) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs index d3114020..a66d2a04 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteFinder.cs @@ -28,7 +28,7 @@ namespace Ocelot.DownstreamRouteFinder.Finder foreach (var reRoute in applicableReRoutes) { - var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern.Template, reRoute.UpstreamTemplatePattern.ContainsQueryString); + var urlMatch = _urlMatcher.Match(upstreamUrlPath, upstreamQueryString, reRoute.UpstreamTemplatePattern); if (urlMatch.Data.Match) { diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs index bac96d53..9d6ffb4e 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/IUrlPathToUrlTemplateMatcher.cs @@ -1,9 +1,10 @@ -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public interface IUrlPathToUrlTemplateMatcher - { - Response Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString); - } +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public interface IUrlPathToUrlTemplateMatcher + { + Response Match(string upstreamUrlPath, string upstreamQueryString, UpstreamPathTemplate pathTemplate); + } } diff --git a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs index ad1117af..84b91304 100644 --- a/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs +++ b/src/Ocelot/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcher.cs @@ -1,24 +1,23 @@ -using System.Text.RegularExpressions; -using Ocelot.Responses; - -namespace Ocelot.DownstreamRouteFinder.UrlMatcher -{ - public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher - { - public Response Match(string upstreamUrlPath, string upstreamQueryString, string upstreamUrlPathTemplate, bool containsQueryString) - { - var regex = new Regex(upstreamUrlPathTemplate); - - if (!containsQueryString) - { - return regex.IsMatch(upstreamUrlPath) - ? new OkResponse(new UrlMatch(true)) - : new OkResponse(new UrlMatch(false)); - } - - return regex.IsMatch($"{upstreamUrlPath}{upstreamQueryString}") - ? new OkResponse(new UrlMatch(true)) - : new OkResponse(new UrlMatch(false)); - } - } -} +using System.Text.RegularExpressions; +using Ocelot.Responses; +using Ocelot.Values; + +namespace Ocelot.DownstreamRouteFinder.UrlMatcher +{ + public class RegExUrlMatcher : IUrlPathToUrlTemplateMatcher + { + public Response Match(string upstreamUrlPath, string upstreamQueryString, UpstreamPathTemplate pathTemplate) + { + if (!pathTemplate.ContainsQueryString) + { + return pathTemplate.Pattern.IsMatch(upstreamUrlPath) + ? new OkResponse(new UrlMatch(true)) + : new OkResponse(new UrlMatch(false)); + } + + return pathTemplate.Pattern.IsMatch($"{upstreamUrlPath}{upstreamQueryString}") + ? new OkResponse(new UrlMatch(true)) + : new OkResponse(new UrlMatch(false)); + } + } +} diff --git a/src/Ocelot/Values/UpstreamPathTemplate.cs b/src/Ocelot/Values/UpstreamPathTemplate.cs index 082b6a83..8497d85e 100644 --- a/src/Ocelot/Values/UpstreamPathTemplate.cs +++ b/src/Ocelot/Values/UpstreamPathTemplate.cs @@ -1,21 +1,28 @@ -namespace Ocelot.Values -{ - public class UpstreamPathTemplate - { - public UpstreamPathTemplate(string template, int priority, bool containsQueryString, string originalValue) - { - Template = template; - Priority = priority; - ContainsQueryString = containsQueryString; - OriginalValue = originalValue; - } - - public string Template { get; } - - public int Priority { get; } - - public bool ContainsQueryString { get; } - - public string OriginalValue { get; } - } -} +using System.Text.RegularExpressions; + +namespace Ocelot.Values +{ + public class UpstreamPathTemplate + { + public UpstreamPathTemplate(string template, int priority, bool containsQueryString, string originalValue) + { + Template = template; + Priority = priority; + ContainsQueryString = containsQueryString; + OriginalValue = originalValue; + Pattern = template == null ? + new Regex("$^", RegexOptions.Compiled | RegexOptions.Singleline) : + new Regex(template, RegexOptions.Compiled | RegexOptions.Singleline); + } + + public string Template { get; } + + public int Priority { get; } + + public bool ContainsQueryString { get; } + + public string OriginalValue { get; } + + public Regex Pattern { get; } + } +} diff --git a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs index 6ff703bd..a7ee0d7e 100644 --- a/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs +++ b/test/Ocelot.Benchmarks/UrlPathToUrlPathTemplateMatcherBenchmarks.cs @@ -1,51 +1,52 @@ -using System; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Validators; -using Ocelot.DownstreamRouteFinder.UrlMatcher; - -namespace Ocelot.Benchmarks -{ - [Config(typeof(UrlPathToUrlPathTemplateMatcherBenchmarks))] - public class UrlPathToUrlPathTemplateMatcherBenchmarks : ManualConfig - { - private RegExUrlMatcher _urlPathMatcher; - private string _downstreamUrlPath; - private string _downstreamUrlPathTemplate; - private string _upstreamQuery; - - public UrlPathToUrlPathTemplateMatcherBenchmarks() - { - Add(StatisticColumn.AllStatistics); - Add(MemoryDiagnoser.Default); - Add(BaselineValidator.FailOnError); - } - - [GlobalSetup] - public void SetUp() - { - _urlPathMatcher = new RegExUrlMatcher(); - _downstreamUrlPath = "api/product/products/1/variants/?soldout=false"; - _downstreamUrlPathTemplate = "api/product/products/{productId}/variants/"; - } - - [Benchmark(Baseline = true)] - public void Baseline() - { - _urlPathMatcher.Match(_downstreamUrlPath, _upstreamQuery, _downstreamUrlPathTemplate, false); - } - - // * Summary * - - // BenchmarkDotNet=v0.10.13, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0] - // Intel Core i5-4278U CPU 2.60GHz (Haswell), 1 CPU, 4 logical cores and 2 physical cores - // .NET Core SDK=2.1.4 - // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | - // ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:| - // Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 | - } -} +using System; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Values; + +namespace Ocelot.Benchmarks +{ + [Config(typeof(UrlPathToUrlPathTemplateMatcherBenchmarks))] + public class UrlPathToUrlPathTemplateMatcherBenchmarks : ManualConfig + { + private RegExUrlMatcher _urlPathMatcher; + private UpstreamPathTemplate _pathTemplate; + private string _downstreamUrlPath; + private string _upstreamQuery; + + public UrlPathToUrlPathTemplateMatcherBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + _urlPathMatcher = new RegExUrlMatcher(); + _pathTemplate = new UpstreamPathTemplate("api/product/products/{productId}/variants/", 0, false, null); + _downstreamUrlPath = "api/product/products/1/variants/?soldout=false"; + } + + [Benchmark(Baseline = true)] + public void Baseline() + { + _urlPathMatcher.Match(_downstreamUrlPath, _upstreamQuery, _pathTemplate); + } + + // * Summary * + + // BenchmarkDotNet=v0.10.13, OS=macOS 10.12.6 (16G1212) [Darwin 16.7.0] + // Intel Core i5-4278U CPU 2.60GHz (Haswell), 1 CPU, 4 logical cores and 2 physical cores + // .NET Core SDK=2.1.4 + // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | + // ----------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|----------:| + // Benchmark1 | 3.133 us | 0.0492 us | 0.0460 us | 0.0119 us | 3.082 us | 3.100 us | 3.122 us | 3.168 us | 3.233 us | 319,161.9 | + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs index 97690a29..7a27833a 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderTests.cs @@ -624,7 +624,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .And(x => x.GivenTheUrlMatcherReturns(new OkResponse(new UrlMatch(true)))) .And(x => x.GivenTheUpstreamHttpMethodIs("Get")) .When(x => x.WhenICallTheFinder()) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1)) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) .BDDfy(); } @@ -677,7 +677,8 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .WithUpstreamPathTemplate(new UpstreamPathTemplate("someUpstreamPath", 1, false, "test")) .Build() ))) - .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(2)) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 0)) + .And(x => x.ThenTheUrlMatcherIsCalledCorrectly(1, 1)) .BDDfy(); } @@ -706,32 +707,32 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder private void ThenTheUrlMatcherIsCalledCorrectly() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.Template, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern), Times.Once); } - private void ThenTheUrlMatcherIsCalledCorrectly(int times) + private void ThenTheUrlMatcherIsCalledCorrectly(int times, int index = 0) { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Exactly(times)); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[index].UpstreamTemplatePattern), Times.Exactly(times)); } private void ThenTheUrlMatcherIsCalledCorrectly(string expectedUpstreamUrlPath) { _mockMatcher - .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Once); + .Verify(x => x.Match(expectedUpstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern), Times.Once); } private void ThenTheUrlMatcherIsNotCalled() { _mockMatcher - .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern.OriginalValue, _reRoutesConfig[0].UpstreamTemplatePattern.ContainsQueryString), Times.Never); + .Verify(x => x.Match(_upstreamUrlPath, _upstreamQuery, _reRoutesConfig[0].UpstreamTemplatePattern), Times.Never); } private void GivenTheUrlMatcherReturns(Response match) { _match = match; _mockMatcher - .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(x => x.Match(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_match); } diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs index 36f2f852..7909456b 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/UrlMatcher/RegExUrlMatcherTests.cs @@ -1,5 +1,6 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Responses; +using Ocelot.Values; using Shouldly; using TestStack.BDDfy; using Xunit; @@ -270,7 +271,7 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder.UrlMatcher private void WhenIMatchThePaths() { - _result = _urlMatcher.Match(_path, _queryString, _downstreamPathTemplate, _containsQueryString); + _result = _urlMatcher.Match(_path, _queryString, new UpstreamPathTemplate(_downstreamPathTemplate, 0, _containsQueryString, _downstreamPathTemplate)); } private void ThenTheResultIsTrue()