From 26a0cf7b5bcad1b031da9c2878aec44ebf39be19 Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Wed, 4 Dec 2024 12:01:14 +0100 Subject: [PATCH 1/8] Benchmarks and more unit tests, plus resulting bug fixes --- SimpleCDN.Benchmarks/Benchmarks.cs | 24 ++++++++ SimpleCDN.Benchmarks/Program.cs | 4 ++ .../SimpleCDN.Benchmarks.csproj | 18 ++++++ SimpleCDN.Tests/NormalizeTests.cs | 28 +++++++++ SimpleCDN.sln | 7 ++- SimpleCDN/Helpers/EnumerableExtensions.cs | 23 ++++++++ SimpleCDN/Helpers/Extensions.cs | 58 +++++++++---------- 7 files changed, 130 insertions(+), 32 deletions(-) create mode 100644 SimpleCDN.Benchmarks/Benchmarks.cs create mode 100644 SimpleCDN.Benchmarks/Program.cs create mode 100644 SimpleCDN.Benchmarks/SimpleCDN.Benchmarks.csproj create mode 100644 SimpleCDN.Tests/NormalizeTests.cs create mode 100644 SimpleCDN/Helpers/EnumerableExtensions.cs diff --git a/SimpleCDN.Benchmarks/Benchmarks.cs b/SimpleCDN.Benchmarks/Benchmarks.cs new file mode 100644 index 0000000..bdaa4f4 --- /dev/null +++ b/SimpleCDN.Benchmarks/Benchmarks.cs @@ -0,0 +1,24 @@ +using BenchmarkDotNet.Attributes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SimpleCDN.Benchmarks +{ + public class Benchmarks + { + const int NormalizationBenchmarkIterationsPerInvoke = 100; + [Benchmark(OperationsPerInvoke = NormalizationBenchmarkIterationsPerInvoke)] + public void NormalizationBenchmark() + { + var path = "/a/b/c/../d/e/f/./d/e/a/../../q/r/s/t/u/./v/w/x/y/z".ToCharArray(); + var span = path.AsSpan(); + for (var i = 0; i < NormalizationBenchmarkIterationsPerInvoke; i++) + { + Helpers.Extensions.Normalize(ref span); + } + } + } +} diff --git a/SimpleCDN.Benchmarks/Program.cs b/SimpleCDN.Benchmarks/Program.cs new file mode 100644 index 0000000..e5d12b5 --- /dev/null +++ b/SimpleCDN.Benchmarks/Program.cs @@ -0,0 +1,4 @@ +using BenchmarkDotNet.Running; +using SimpleCDN.Benchmarks; + +BenchmarkRunner.Run(); \ No newline at end of file diff --git a/SimpleCDN.Benchmarks/SimpleCDN.Benchmarks.csproj b/SimpleCDN.Benchmarks/SimpleCDN.Benchmarks.csproj new file mode 100644 index 0000000..ab6926e --- /dev/null +++ b/SimpleCDN.Benchmarks/SimpleCDN.Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + diff --git a/SimpleCDN.Tests/NormalizeTests.cs b/SimpleCDN.Tests/NormalizeTests.cs new file mode 100644 index 0000000..b2f2116 --- /dev/null +++ b/SimpleCDN.Tests/NormalizeTests.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SimpleCDN.Tests +{ + [TestFixture] + public class NormalizeTests + { + [TestCase("/aaaa/../bbbb", "/bbbb")] + [TestCase("/aaaa/./bbbb", "/aaaa/bbbb")] + [TestCase("/aaaa/./bbbb/./cccc", "/aaaa/bbbb/cccc")] + [TestCase("/../aaaa/./bbbb/./cccc", "/aaaa/bbbb/cccc")] + [TestCase("/aaaa/./bbbb/./cccc/..", "/aaaa/bbbb")] + [TestCase("/aaaa/./bbbb/./cccc/../../dddd", "/aaaa/dddd")] + public void Test_Normalize_Normalizes(string path, string expected) + { + var pathChars = path.ToCharArray(); + var span = pathChars.AsSpan(); + + Helpers.Extensions.Normalize(ref span); + + Assert.That(span.ToString(), Is.EqualTo(expected)); + } + } +} diff --git a/SimpleCDN.sln b/SimpleCDN.sln index 2ee3494..8b1f9e5 100644 --- a/SimpleCDN.sln +++ b/SimpleCDN.sln @@ -10,6 +10,8 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Tests.Integration", "SimpleCDN.Tests.Integration\SimpleCDN.Tests.Integration.csproj", "{F79E71E8-89D8-46F7-802C-CFDF3A77447D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Benchmarks", "SimpleCDN.Benchmarks\SimpleCDN.Benchmarks.csproj", "{73BC9966-46FF-40D8-89F4-C990B15370A2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,9 +31,12 @@ Global {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.ActiveCfg = Release|Any CPU {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.Build.0 = Release|Any CPU {F79E71E8-89D8-46F7-802C-CFDF3A77447D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F79E71E8-89D8-46F7-802C-CFDF3A77447D}.Debug|Any CPU.Build.0 = Debug|Any CPU {F79E71E8-89D8-46F7-802C-CFDF3A77447D}.Release|Any CPU.ActiveCfg = Release|Any CPU {F79E71E8-89D8-46F7-802C-CFDF3A77447D}.Release|Any CPU.Build.0 = Release|Any CPU + {73BC9966-46FF-40D8-89F4-C990B15370A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73BC9966-46FF-40D8-89F4-C990B15370A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73BC9966-46FF-40D8-89F4-C990B15370A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73BC9966-46FF-40D8-89F4-C990B15370A2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SimpleCDN/Helpers/EnumerableExtensions.cs b/SimpleCDN/Helpers/EnumerableExtensions.cs new file mode 100644 index 0000000..10d8d6b --- /dev/null +++ b/SimpleCDN/Helpers/EnumerableExtensions.cs @@ -0,0 +1,23 @@ +namespace SimpleCDN.Helpers +{ + public static class EnumerableExtensions + { + public static (T left, IEnumerable right) RemoveFirst(this IEnumerable source) + { + var enumerator = source.GetEnumerator(); + + if (!enumerator.MoveNext()) + ArgumentOutOfRangeException.ThrowIfLessThan(0, 1, nameof(source)); + + return (enumerator.Current, RestEnumerator(enumerator)); + + static IEnumerable RestEnumerator(IEnumerator enumerator) + { + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + } + } + } +} diff --git a/SimpleCDN/Helpers/Extensions.cs b/SimpleCDN/Helpers/Extensions.cs index 6fc842e..0eeef2f 100644 --- a/SimpleCDN/Helpers/Extensions.cs +++ b/SimpleCDN/Helpers/Extensions.cs @@ -34,64 +34,54 @@ public static string FormatByteCount(this long number) return $"{result:0.##}{sizeNames[sizeNameIndex]}B"; } - public static (T left, IEnumerable right) RemoveFirst(this IEnumerable source) - { - var enumerator = source.GetEnumerator(); - - if (!enumerator.MoveNext()) - ArgumentOutOfRangeException.ThrowIfLessThan(0, 1, nameof(source)); - - return (enumerator.Current, RestEnumerator(enumerator)); - - static IEnumerable RestEnumerator(IEnumerator enumerator) - { - while (enumerator.MoveNext()) - { - yield return enumerator.Current; - } - } - } - + /// + /// Normalizes a path by removing all . and .. segments in-place. When ready, + /// will be shortened to contain just the normalized path. + /// + /// The path to normalize in-place public static void Normalize(ref this Span path) { var segments = MemoryExtensions.Split(path, '/'); - var segmentsToRemove = new List(); + var rangesToRemove = new List(); - Range lastSegment = Range.All; + var resultRanges = new Stack(); + + var originalLength = path.Length; foreach (var segment in segments) { - if (segment.GetOffsetAndLength(path.Length).Length == 0) + if (segment.GetOffsetAndLength(originalLength).Length == 0) { continue; } if (path[segment] is ['.', '.']) { - if (!lastSegment.Equals(Range.All)) + if (resultRanges.TryPop(out Range lastSegment)) { - segmentsToRemove.Add(lastSegment); + rangesToRemove.Add(lastSegment); } - segmentsToRemove.Add(segment); + rangesToRemove.Add(segment); } else if (path[segment] is ['.']) { // if the segment is . it should be removed - segmentsToRemove.Add(segment); + rangesToRemove.Add(segment); + } else + { + resultRanges.Push(segment); } - - lastSegment = segment; } int offset = 0; - foreach (var segmentToRemove in segmentsToRemove) - { - // transform path, so that + var segmentsToRemove = rangesToRemove.Select(s => s.GetOffsetAndLength(originalLength)).OrderBy(s => s.Offset); - var (start, length) = segmentToRemove.GetOffsetAndLength(path.Length); + foreach (var segment in segmentsToRemove) + { + var (start, length) = segment; // include the / before the segment // and subtract the offset to account for previously removed segments @@ -123,6 +113,12 @@ public static void Normalize(ref this Span path) } } + /// + /// Sanitizes a string for use in log messages:
+ /// - replaces all whitespace (including newlines, tabs, ...) with a single space + ///
+ /// + /// public static string ForLog(this string input) => WhitespaceRegex().Replace(input, " "); [GeneratedRegex(@"\s+", RegexOptions.Multiline | RegexOptions.Compiled)] From f315b7380ca1ea9546fcaae38ec929ad95486e5e Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Wed, 4 Dec 2024 12:25:57 +0100 Subject: [PATCH 2/8] change benchmarks --- SimpleCDN.Benchmarks/Benchmarks.cs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/SimpleCDN.Benchmarks/Benchmarks.cs b/SimpleCDN.Benchmarks/Benchmarks.cs index bdaa4f4..78aa97e 100644 --- a/SimpleCDN.Benchmarks/Benchmarks.cs +++ b/SimpleCDN.Benchmarks/Benchmarks.cs @@ -1,20 +1,42 @@ using BenchmarkDotNet.Attributes; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SimpleCDN.Benchmarks { + [MemoryDiagnoser(false)] + [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Needed for BenchmarkDotNet")] public class Benchmarks { + public IEnumerable Paths => + [ + "/".ToCharArray(), + "/../".ToCharArray(), + "/data/../".ToCharArray(), + "/test.txt".ToCharArray(), + "/data/../test.txt".ToCharArray(), + "/data/test.json".ToCharArray(), + "/data/../data/test.json".ToCharArray(), + "/data/./../data/test.json".ToCharArray(), + "/data/data/../../data/test.json".ToCharArray(), + "/data/data/../../data/../data/test.json".ToCharArray(), + "/data/data/../../data/../data/../data/test.json".ToCharArray(), + "/data/data/.././data/../../../data/../data/test.json".ToCharArray(), + ]; + + [ParamsSource(nameof(Paths))] + public char[] Path { get; set; } = []; + const int NormalizationBenchmarkIterationsPerInvoke = 100; + [Benchmark(OperationsPerInvoke = NormalizationBenchmarkIterationsPerInvoke)] public void NormalizationBenchmark() { - var path = "/a/b/c/../d/e/f/./d/e/a/../../q/r/s/t/u/./v/w/x/y/z".ToCharArray(); - var span = path.AsSpan(); + var span = Path.AsSpan(); for (var i = 0; i < NormalizationBenchmarkIterationsPerInvoke; i++) { Helpers.Extensions.Normalize(ref span); From 191ea2186fd91dbf8bafd9d848dfe1c9700f70d1 Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Wed, 4 Dec 2024 12:26:43 +0100 Subject: [PATCH 3/8] error suppression was not needed anymore --- SimpleCDN.Benchmarks/Benchmarks.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SimpleCDN.Benchmarks/Benchmarks.cs b/SimpleCDN.Benchmarks/Benchmarks.cs index 78aa97e..f1f3220 100644 --- a/SimpleCDN.Benchmarks/Benchmarks.cs +++ b/SimpleCDN.Benchmarks/Benchmarks.cs @@ -9,7 +9,6 @@ namespace SimpleCDN.Benchmarks { [MemoryDiagnoser(false)] - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Needed for BenchmarkDotNet")] public class Benchmarks { public IEnumerable Paths => From 35fd2cf686f6350af865098812dc1d294524403e Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Thu, 5 Dec 2024 00:05:04 +0100 Subject: [PATCH 4/8] improve benchmarks --- SimpleCDN.Benchmarks/Benchmarks.cs | 27 +++++++------------ SimpleCDN.Benchmarks/GlobalSuppressions.cs | 8 ++++++ .../Properties/launchSettings.json | 7 +++++ launchSettings.json | 3 +++ 4 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 SimpleCDN.Benchmarks/GlobalSuppressions.cs create mode 100644 SimpleCDN.Benchmarks/Properties/launchSettings.json diff --git a/SimpleCDN.Benchmarks/Benchmarks.cs b/SimpleCDN.Benchmarks/Benchmarks.cs index f1f3220..0ebf058 100644 --- a/SimpleCDN.Benchmarks/Benchmarks.cs +++ b/SimpleCDN.Benchmarks/Benchmarks.cs @@ -1,4 +1,5 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -9,35 +10,27 @@ namespace SimpleCDN.Benchmarks { [MemoryDiagnoser(false)] + //[SimpleJob(RuntimeMoniker.NativeAot90)] + [SimpleJob(RuntimeMoniker.Net90)] public class Benchmarks { - public IEnumerable Paths => + public static IEnumerable Paths => [ - "/".ToCharArray(), - "/../".ToCharArray(), - "/data/../".ToCharArray(), "/test.txt".ToCharArray(), - "/data/../test.txt".ToCharArray(), - "/data/test.json".ToCharArray(), - "/data/../data/test.json".ToCharArray(), - "/data/./../data/test.json".ToCharArray(), - "/data/data/../../data/test.json".ToCharArray(), - "/data/data/../../data/../data/test.json".ToCharArray(), - "/data/data/../../data/../data/../data/test.json".ToCharArray(), "/data/data/.././data/../../../data/../data/test.json".ToCharArray(), + "/data/data/../../data/../data/../data/../data/./../data/data/../data/../data/../data/./../data/test/data/../../test/data/../../data/../data/test.json".ToCharArray(), ]; - [ParamsSource(nameof(Paths))] - public char[] Path { get; set; } = []; - - const int NormalizationBenchmarkIterationsPerInvoke = 100; + const int NormalizationBenchmarkIterationsPerInvoke = 50; [Benchmark(OperationsPerInvoke = NormalizationBenchmarkIterationsPerInvoke)] - public void NormalizationBenchmark() + [ArgumentsSource(nameof(Paths))] + public void NormalizationBenchmark(char[] path) { - var span = Path.AsSpan(); for (var i = 0; i < NormalizationBenchmarkIterationsPerInvoke; i++) { + var copy = path.ToArray(); + var span = copy.AsSpan(); Helpers.Extensions.Normalize(ref span); } } diff --git a/SimpleCDN.Benchmarks/GlobalSuppressions.cs b/SimpleCDN.Benchmarks/GlobalSuppressions.cs new file mode 100644 index 0000000..1499341 --- /dev/null +++ b/SimpleCDN.Benchmarks/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "BenchmarkDotnet requires some members to be non-static, even if they could be static.")] diff --git a/SimpleCDN.Benchmarks/Properties/launchSettings.json b/SimpleCDN.Benchmarks/Properties/launchSettings.json new file mode 100644 index 0000000..e31c6bc --- /dev/null +++ b/SimpleCDN.Benchmarks/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "SimpleCDN.Benchmarks": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/launchSettings.json b/launchSettings.json index 00d9350..b9bebba 100644 --- a/launchSettings.json +++ b/launchSettings.json @@ -3,6 +3,9 @@ "Docker Compose": { "commandName": "DockerCompose", "commandVersion": "1.0", + "composeLaunchAction": "LaunchBrowser", + "composeLaunchServiceName": "simplecdn", + "composeLaunchUrl": "{Scheme}://localhost:{ServicePort}", "serviceActions": { "simplecdn": "StartDebugging" } From de01f5bb3371a82e2bc2192d6d3a9d6eb2f855ef Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Thu, 5 Dec 2024 10:21:06 +0100 Subject: [PATCH 5/8] fix Visual Studio configuration error --- SimpleCDN.sln | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/SimpleCDN.sln b/SimpleCDN.sln index 8b1f9e5..cf02e12 100644 --- a/SimpleCDN.sln +++ b/SimpleCDN.sln @@ -2,15 +2,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.13.35507.96 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN", "SimpleCDN\SimpleCDN.csproj", "{E15F4BC2-E820-48D6-857B-E2DC9E35D370}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Tests", "SimpleCDN.Tests\SimpleCDN.Tests.csproj", "{6D8833B9-7EC8-4212-83DD-ABCCD677309E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN", "SimpleCDN\SimpleCDN.csproj", "{9C7F11BA-ECF0-8CD1-D1F5-DB31D692724E}" EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{81DDED9D-158B-E303-5F62-77A2896D2A5A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Tests.Integration", "SimpleCDN.Tests.Integration\SimpleCDN.Tests.Integration.csproj", "{F79E71E8-89D8-46F7-802C-CFDF3A77447D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Tests", "SimpleCDN.Tests\SimpleCDN.Tests.csproj", "{9F41AC4B-73A5-AED7-2FAD-13532040B2E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Benchmarks", "SimpleCDN.Benchmarks\SimpleCDN.Benchmarks.csproj", "{01B35836-CD1D-0631-A280-04996E3E2058}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Benchmarks", "SimpleCDN.Benchmarks\SimpleCDN.Benchmarks.csproj", "{73BC9966-46FF-40D8-89F4-C990B15370A2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCDN.Tests.Integration", "SimpleCDN.Tests.Integration\SimpleCDN.Tests.Integration.csproj", "{748DF05A-61E9-3214-6751-2FB8D9C75A1C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,30 +18,28 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E15F4BC2-E820-48D6-857B-E2DC9E35D370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E15F4BC2-E820-48D6-857B-E2DC9E35D370}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E15F4BC2-E820-48D6-857B-E2DC9E35D370}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E15F4BC2-E820-48D6-857B-E2DC9E35D370}.Release|Any CPU.Build.0 = Release|Any CPU - {6D8833B9-7EC8-4212-83DD-ABCCD677309E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D8833B9-7EC8-4212-83DD-ABCCD677309E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D8833B9-7EC8-4212-83DD-ABCCD677309E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D8833B9-7EC8-4212-83DD-ABCCD677309E}.Release|Any CPU.Build.0 = Release|Any CPU + {9C7F11BA-ECF0-8CD1-D1F5-DB31D692724E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C7F11BA-ECF0-8CD1-D1F5-DB31D692724E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C7F11BA-ECF0-8CD1-D1F5-DB31D692724E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C7F11BA-ECF0-8CD1-D1F5-DB31D692724E}.Release|Any CPU.Build.0 = Release|Any CPU {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Debug|Any CPU.Build.0 = Debug|Any CPU {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.ActiveCfg = Release|Any CPU {81DDED9D-158B-E303-5F62-77A2896D2A5A}.Release|Any CPU.Build.0 = Release|Any CPU - {F79E71E8-89D8-46F7-802C-CFDF3A77447D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F79E71E8-89D8-46F7-802C-CFDF3A77447D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F79E71E8-89D8-46F7-802C-CFDF3A77447D}.Release|Any CPU.Build.0 = Release|Any CPU - {73BC9966-46FF-40D8-89F4-C990B15370A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {73BC9966-46FF-40D8-89F4-C990B15370A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {73BC9966-46FF-40D8-89F4-C990B15370A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {73BC9966-46FF-40D8-89F4-C990B15370A2}.Release|Any CPU.Build.0 = Release|Any CPU + {9F41AC4B-73A5-AED7-2FAD-13532040B2E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F41AC4B-73A5-AED7-2FAD-13532040B2E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F41AC4B-73A5-AED7-2FAD-13532040B2E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F41AC4B-73A5-AED7-2FAD-13532040B2E1}.Release|Any CPU.Build.0 = Release|Any CPU + {01B35836-CD1D-0631-A280-04996E3E2058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01B35836-CD1D-0631-A280-04996E3E2058}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01B35836-CD1D-0631-A280-04996E3E2058}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01B35836-CD1D-0631-A280-04996E3E2058}.Release|Any CPU.Build.0 = Release|Any CPU + {748DF05A-61E9-3214-6751-2FB8D9C75A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {748DF05A-61E9-3214-6751-2FB8D9C75A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {748DF05A-61E9-3214-6751-2FB8D9C75A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {748DF05A-61E9-3214-6751-2FB8D9C75A1C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D252A113-93EA-49FE-9080-E3A41CD60F25} - EndGlobalSection EndGlobal From 66c35dc69ce4ca825c2c1e94df3a92d7f6fdb80e Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Thu, 5 Dec 2024 10:22:31 +0100 Subject: [PATCH 6/8] code cleanup --- SimpleCDN.Benchmarks/Benchmarks.cs | 6 ------ SimpleCDN.Tests.Integration/EndpointTests.cs | 4 +--- SimpleCDN.Tests/CacheManagerTests.cs | 4 ---- SimpleCDN.Tests/Mocks/DistributedCacheMock.cs | 5 ----- SimpleCDN.Tests/NormalizeTests.cs | 8 +------- 5 files changed, 2 insertions(+), 25 deletions(-) diff --git a/SimpleCDN.Benchmarks/Benchmarks.cs b/SimpleCDN.Benchmarks/Benchmarks.cs index dcf2854..4080875 100644 --- a/SimpleCDN.Benchmarks/Benchmarks.cs +++ b/SimpleCDN.Benchmarks/Benchmarks.cs @@ -1,11 +1,5 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SimpleCDN.Benchmarks { diff --git a/SimpleCDN.Tests.Integration/EndpointTests.cs b/SimpleCDN.Tests.Integration/EndpointTests.cs index c2fd9b5..90a2d45 100644 --- a/SimpleCDN.Tests.Integration/EndpointTests.cs +++ b/SimpleCDN.Tests.Integration/EndpointTests.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNetCore.Mvc.Testing; - -namespace SimpleCDN.Tests.Integration +namespace SimpleCDN.Tests.Integration { public class EndpointTests : IClassFixture { diff --git a/SimpleCDN.Tests/CacheManagerTests.cs b/SimpleCDN.Tests/CacheManagerTests.cs index 3b7b51a..3df5c0a 100644 --- a/SimpleCDN.Tests/CacheManagerTests.cs +++ b/SimpleCDN.Tests/CacheManagerTests.cs @@ -1,11 +1,7 @@ using SimpleCDN.Cache; using SimpleCDN.Services; using SimpleCDN.Tests.Mocks; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; namespace SimpleCDN.Tests { diff --git a/SimpleCDN.Tests/Mocks/DistributedCacheMock.cs b/SimpleCDN.Tests/Mocks/DistributedCacheMock.cs index 666f251..950ab0d 100644 --- a/SimpleCDN.Tests/Mocks/DistributedCacheMock.cs +++ b/SimpleCDN.Tests/Mocks/DistributedCacheMock.cs @@ -1,9 +1,4 @@ using Microsoft.Extensions.Caching.Distributed; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SimpleCDN.Tests.Mocks { diff --git a/SimpleCDN.Tests/NormalizeTests.cs b/SimpleCDN.Tests/NormalizeTests.cs index b2f2116..6e6258d 100644 --- a/SimpleCDN.Tests/NormalizeTests.cs +++ b/SimpleCDN.Tests/NormalizeTests.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SimpleCDN.Tests +namespace SimpleCDN.Tests { [TestFixture] public class NormalizeTests From e76eb0fd1c13349d9631d7fdded93a6e60b7f090 Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Thu, 5 Dec 2024 10:31:46 +0100 Subject: [PATCH 7/8] prevent benchmark to be auto-optimized --- SimpleCDN.Benchmarks/Benchmarks.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/SimpleCDN.Benchmarks/Benchmarks.cs b/SimpleCDN.Benchmarks/Benchmarks.cs index 4080875..80ae6bb 100644 --- a/SimpleCDN.Benchmarks/Benchmarks.cs +++ b/SimpleCDN.Benchmarks/Benchmarks.cs @@ -3,9 +3,9 @@ namespace SimpleCDN.Benchmarks { - [MemoryDiagnoser(false)] //[SimpleJob(RuntimeMoniker.NativeAot90)] [SimpleJob(RuntimeMoniker.Net90)] + [MemoryDiagnoser(false)] public class Benchmarks { public static IEnumerable Paths => @@ -19,14 +19,19 @@ public class Benchmarks [Benchmark(OperationsPerInvoke = NormalizationBenchmarkIterationsPerInvoke)] [ArgumentsSource(nameof(Paths))] - public void Normalize(char[] path) + public long Normalize(char[] path) { + long res = 0; + for (var i = 0; i < NormalizationBenchmarkIterationsPerInvoke; i++) { var copy = path.ToArray(); var span = copy.AsSpan(); Helpers.Extensions.Normalize(ref span); + res += span.BinarySearch('/'); } + + return res; } } } From 305c65df31314c76b5f4414cad962e1438106212 Mon Sep 17 00:00:00 2001 From: Jonathan Bout Date: Thu, 5 Dec 2024 15:03:41 +0100 Subject: [PATCH 8/8] properly configure output caching --- SimpleCDN/Endpoints/CDN.cs | 2 +- SimpleCDN/Program.cs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/SimpleCDN/Endpoints/CDN.cs b/SimpleCDN/Endpoints/CDN.cs index 9bd43f8..242d8c1 100644 --- a/SimpleCDN/Endpoints/CDN.cs +++ b/SimpleCDN/Endpoints/CDN.cs @@ -13,7 +13,6 @@ public static IEndpointRouteBuilder RegisterCDNEndpoints(this IEndpointRouteBuil { try { - if (loader.GetFile(route) is CDNFile file) { var typedAccept = ctx.Request.GetTypedHeaders().Accept; @@ -57,6 +56,7 @@ public static IEndpointRouteBuilder RegisterCDNEndpoints(this IEndpointRouteBuil }).CacheOutput(policy => { // cache the response for 1 minute to reduce load on the server + // this is another layer of caching, on top of the CDNLoader cache. policy.Cache() .Expire(TimeSpan.FromMinutes(1)) .SetVaryByRouteValue("route") diff --git a/SimpleCDN/Program.cs b/SimpleCDN/Program.cs index 51e6ffd..77f8b6f 100644 --- a/SimpleCDN/Program.cs +++ b/SimpleCDN/Program.cs @@ -20,6 +20,15 @@ private static void Main(string[] args) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddCommandLine(args); + builder.Services.AddOutputCache(options => + { + var configuration = options.ApplicationServices.GetRequiredService(); + + // output cache is 1/10th of memory cache + // in bytes in kilobytes + options.MaximumBodySize = configuration.MaxMemoryCacheSize * 100; + }); + builder.Services.MapConfiguration(); // for now, we use a simple size-limited in-memory cache. @@ -35,6 +44,7 @@ private static void Main(string[] args) var app = builder.Build(); app.RegisterCDNEndpoints(); + app.UseOutputCache(); app.Run(); }