|
6 | 6 | using System.Threading.Tasks; |
7 | 7 | using Shouldly; |
8 | 8 | using Xunit; |
| 9 | +using Xunit.Abstractions; |
9 | 10 |
|
10 | 11 | namespace ManagedCode.MimeTypes.Tests; |
11 | 12 |
|
12 | 13 | [Collection(MimeHelperMutableStateCollection.Name)] |
13 | 14 | public sealed class MimeHelperPerformanceTests |
14 | 15 | { |
15 | 16 | private static readonly TimeSpan HotPathBudget = TimeSpan.FromSeconds(2); |
| 17 | + private readonly ITestOutputHelper _output; |
| 18 | + |
| 19 | + public MimeHelperPerformanceTests(ITestOutputHelper output) |
| 20 | + { |
| 21 | + _output = output; |
| 22 | + } |
16 | 23 |
|
17 | 24 | [Fact] |
18 | 25 | [Trait("Category", "Performance")] |
@@ -107,37 +114,82 @@ public void ParallelLookups_ShouldStayWithinBudget() |
107 | 114 | [Trait("Category", "Performance")] |
108 | 115 | public void ColdProcessStartup_ShouldStayBelowCatastrophicRegressionBudget() |
109 | 116 | { |
110 | | - var probe = FindStartupProbe(); |
111 | 117 | var durations = new List<TimeSpan>(); |
112 | 118 |
|
113 | 119 | for (var index = 0; index < 3; index++) |
114 | 120 | { |
115 | | - var startInfo = new ProcessStartInfo("dotnet") |
116 | | - { |
117 | | - RedirectStandardOutput = true, |
118 | | - RedirectStandardError = true, |
119 | | - UseShellExecute = false |
120 | | - }; |
121 | | - startInfo.ArgumentList.Add(probe); |
122 | | - |
123 | | - var stopwatch = Stopwatch.StartNew(); |
124 | | - using var process = Process.Start(startInfo); |
125 | | - process.ShouldNotBeNull(); |
126 | | - var output = process.StandardOutput.ReadToEnd(); |
127 | | - var error = process.StandardError.ReadToEnd(); |
128 | | - process.WaitForExit(); |
129 | | - stopwatch.Stop(); |
130 | | - |
131 | | - process.ExitCode.ShouldBe(0, error); |
132 | | - output.Trim().ShouldStartWith("image/png|"); |
133 | | - var allocated = long.Parse(output.Trim().Split('|')[3], System.Globalization.CultureInfo.InvariantCulture); |
134 | | - allocated.ShouldBeLessThanOrEqualTo(256); |
135 | | - durations.Add(stopwatch.Elapsed); |
| 121 | + var result = RunStartupProbe(null, "default"); |
| 122 | + result.Mime.ShouldBe("image/png"); |
| 123 | + result.Allocated.ShouldBeLessThanOrEqualTo(256); |
| 124 | + durations.Add(result.Duration); |
136 | 125 | } |
137 | 126 |
|
138 | 127 | durations.Max().ShouldBeLessThan(TimeSpan.FromSeconds(2)); |
139 | 128 | } |
140 | 129 |
|
| 130 | + [Theory] |
| 131 | + [Trait("Category", "Performance")] |
| 132 | + [InlineData("default", "https://cdn.example.test/assets/photo.png?v=1", "image/png")] |
| 133 | + [InlineData("tiered", "https://cdn.example.test/assets/photo.png?v=1", "image/png")] |
| 134 | + [InlineData("optimized", "https://cdn.example.test/assets/photo.png?v=1", "image/png")] |
| 135 | + [InlineData("default", "png", "image/png")] |
| 136 | + [InlineData("tiered", "assets/photo.png", "image/png")] |
| 137 | + [InlineData("tiered", "HTTPS://cdn.example.test/assets/PHOTO.PNG#preview?file=doc.pdf", "image/png")] |
| 138 | + [InlineData("tiered", "types/module.d.ts", "application/typescript")] |
| 139 | + [InlineData("optimized", "archive.tar.gz?download=1", "application/gzip")] |
| 140 | + [InlineData("tiered", "unknown.no-extension", "application/octet-stream")] |
| 141 | + public void ColdProcessLookups_ShouldPreserveAllocationBudgetAcrossJitModes( |
| 142 | + string runtimeMode, string input, string expectedMime) |
| 143 | + { |
| 144 | + var result = RunStartupProbe(input, runtimeMode); |
| 145 | + |
| 146 | + result.Mime.ShouldBe(expectedMime); |
| 147 | + result.Allocated.ShouldBeLessThanOrEqualTo(256); |
| 148 | + } |
| 149 | + |
| 150 | + private (TimeSpan Duration, long Allocated, string Mime) RunStartupProbe(string? input, string runtimeMode) |
| 151 | + { |
| 152 | + var startInfo = new ProcessStartInfo("dotnet") |
| 153 | + { |
| 154 | + RedirectStandardOutput = true, |
| 155 | + RedirectStandardError = true, |
| 156 | + UseShellExecute = false |
| 157 | + }; |
| 158 | + startInfo.ArgumentList.Add(FindStartupProbe()); |
| 159 | + if (input != null) |
| 160 | + { |
| 161 | + startInfo.ArgumentList.Add(input); |
| 162 | + } |
| 163 | + |
| 164 | + if (runtimeMode == "tiered") |
| 165 | + { |
| 166 | + startInfo.Environment["DOTNET_TieredCompilation"] = "1"; |
| 167 | + startInfo.Environment["DOTNET_TieredPGO"] = "1"; |
| 168 | + startInfo.Environment["DOTNET_TC_QuickJitForLoops"] = "1"; |
| 169 | + startInfo.Environment["DOTNET_TC_OnStackReplacement"] = "1"; |
| 170 | + startInfo.Environment["DOTNET_TC_CallCountingDelayMs"] = "0"; |
| 171 | + } |
| 172 | + else if (runtimeMode == "optimized") |
| 173 | + { |
| 174 | + startInfo.Environment["DOTNET_TieredCompilation"] = "0"; |
| 175 | + } |
| 176 | + |
| 177 | + var stopwatch = Stopwatch.StartNew(); |
| 178 | + using var process = Process.Start(startInfo); |
| 179 | + process.ShouldNotBeNull(); |
| 180 | + var output = process.StandardOutput.ReadToEnd(); |
| 181 | + var error = process.StandardError.ReadToEnd(); |
| 182 | + process.WaitForExit(); |
| 183 | + stopwatch.Stop(); |
| 184 | + |
| 185 | + process.ExitCode.ShouldBe(0, error); |
| 186 | + var parts = output.Trim().Split('|'); |
| 187 | + parts.Length.ShouldBe(4, output); |
| 188 | + var allocated = long.Parse(parts[3], System.Globalization.CultureInfo.InvariantCulture); |
| 189 | + _output.WriteLine($"runtime={runtimeMode}; input={input ?? "default"}; result={output.Trim()}; elapsed={stopwatch.Elapsed}"); |
| 190 | + return (stopwatch.Elapsed, allocated, parts[0]); |
| 191 | + } |
| 192 | + |
141 | 193 | private static TimeSpan Measure<T>(int iterations, Func<T> operation) |
142 | 194 | { |
143 | 195 | var stopwatch = Stopwatch.StartNew(); |
|
0 commit comments