Skip to content

Commit 59ed462

Browse files
committed
fix: isolate cold-start allocation measurements for 10.1.3
1 parent 605d8cd commit 59ed462

5 files changed

Lines changed: 104 additions & 26 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ jobs:
3232
- name: Test including performance budgets
3333
run: dotnet test --configuration Release --no-build --verbosity normal
3434

35+
- name: Preserve full test results
36+
if: always()
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: full-test-results
40+
path: ./**/TestResults/*.trx
41+
retention-days: 5
42+
3543
# Coverage instrumentation changes allocations and startup timing. Keep all
3644
# performance assertions in the uninstrumented full suite above.
3745
- name: Collect functional coverage

‎.github/workflows/release.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ jobs:
4141
- name: Test
4242
run: dotnet test --configuration Release --no-build --verbosity normal
4343

44+
- name: Preserve full test results
45+
if: always()
46+
uses: actions/upload-artifact@v4
47+
with:
48+
name: full-test-results
49+
path: ./**/TestResults/*.trx
50+
retention-days: 5
51+
4452
- name: Pack NuGet packages
4553
run: dotnet pack --configuration Release --no-build --output ./artifacts
4654

‎Directory.Build.props‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
<RepositoryUrl>https://github.com/managedcode/MimeTypes</RepositoryUrl>
1818
<PackageProjectUrl>https://github.com/managedcode/MimeTypes</PackageProjectUrl>
1919
<Product>Managed Code - MimeTypes</Product>
20-
<Version>10.1.2</Version>
21-
<PackageVersion>10.1.2</PackageVersion>
20+
<Version>10.1.3</Version>
21+
<PackageVersion>10.1.3</PackageVersion>
2222

2323
</PropertyGroup>
2424
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1+
using System.Runtime.CompilerServices;
12
using ManagedCode.MimeTypes;
23

4+
var input = args.Length == 0 ? "https://cdn.example.test/assets/photo.png?v=1" : args[0];
35
var mime = MimeHelper.GetMimeType("asset.png");
46
var extensions = MimeHelper.GetExtensions(mime);
57
var knownMimeTypes = MimeHelper.GetKnownMimeTypes();
68
var before = GC.GetAllocatedBytesForCurrentThread();
79
for (var index = 0; index < 100_000; index++)
810
{
9-
_ = MimeHelper.GetMimeType("https://cdn.example.test/assets/photo.png?v=1");
11+
mime = MimeHelper.GetMimeType(input);
1012
}
1113
var allocated = GC.GetAllocatedBytesForCurrentThread() - before;
1214

13-
Console.WriteLine($"{mime}|{extensions.Count}|{knownMimeTypes.Count}|{allocated}");
15+
Report(mime, extensions.Count, knownMimeTypes.Count, allocated);
16+
17+
// Keep interpolated output in a separate JIT body. Otherwise OSR can initialize
18+
// formatting infrastructure while the lookup allocation counter is running.
19+
[MethodImpl(MethodImplOptions.NoInlining)]
20+
static void Report(string mime, int extensionCount, int knownCount, long allocated)
21+
{
22+
Console.WriteLine($"{mime}|{extensionCount}|{knownCount}|{allocated}");
23+
}

‎ManagedCode.MimeTypes.Tests/MimeHelperPerformanceTests.cs‎

Lines changed: 74 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
using System.Threading.Tasks;
77
using Shouldly;
88
using Xunit;
9+
using Xunit.Abstractions;
910

1011
namespace ManagedCode.MimeTypes.Tests;
1112

1213
[Collection(MimeHelperMutableStateCollection.Name)]
1314
public sealed class MimeHelperPerformanceTests
1415
{
1516
private static readonly TimeSpan HotPathBudget = TimeSpan.FromSeconds(2);
17+
private readonly ITestOutputHelper _output;
18+
19+
public MimeHelperPerformanceTests(ITestOutputHelper output)
20+
{
21+
_output = output;
22+
}
1623

1724
[Fact]
1825
[Trait("Category", "Performance")]
@@ -107,37 +114,82 @@ public void ParallelLookups_ShouldStayWithinBudget()
107114
[Trait("Category", "Performance")]
108115
public void ColdProcessStartup_ShouldStayBelowCatastrophicRegressionBudget()
109116
{
110-
var probe = FindStartupProbe();
111117
var durations = new List<TimeSpan>();
112118

113119
for (var index = 0; index < 3; index++)
114120
{
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);
136125
}
137126

138127
durations.Max().ShouldBeLessThan(TimeSpan.FromSeconds(2));
139128
}
140129

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+
141193
private static TimeSpan Measure<T>(int iterations, Func<T> operation)
142194
{
143195
var stopwatch = Stopwatch.StartNew();

0 commit comments

Comments
 (0)