Skip to content

Commit 317e2f6

Browse files
committed
ci: adds a comparison project for benchmark results
Signed-off-by: Vincent Biret <[email protected]>
1 parent f39bc77 commit 317e2f6

File tree

6 files changed

+174
-3
lines changed

6 files changed

+174
-3
lines changed

.vscode/launch.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,36 @@
1919
"console": "internalConsole",
2020
"stopAtEntry": false
2121
},
22+
{
23+
// Use IntelliSense to find out which attributes exist for C# debugging
24+
// Use hover for the description of the existing attributes
25+
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/main/debugger-launchjson.md
26+
"name": "Launch Benchmark",
27+
"type": "coreclr",
28+
"request": "launch",
29+
"preLaunchTask": "build",
30+
// If you have changed target frameworks, make sure to update the program path.
31+
"program": "${workspaceFolder}/performance/benchmark/bin/Release/net8.0/PerformanceTests.dll",
32+
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
33+
"console": "internalConsole",
34+
"stopAtEntry": false,
35+
},
36+
{
37+
// Use IntelliSense to find out which attributes exist for C# debugging
38+
// Use hover for the description of the existing attributes
39+
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/main/debugger-launchjson.md
40+
"name": "Launch Results comparer",
41+
"type": "coreclr",
42+
"request": "launch",
43+
"preLaunchTask": "build",
44+
// If you have changed target frameworks, make sure to update the program path.
45+
"program": "${workspaceFolder}/performance/resultsComparer/bin/Debug/net8.0/resultsComparer.dll",
46+
"cwd": "${workspaceFolder}/performance/resultsComparer",
47+
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
48+
"console": "internalConsole",
49+
"stopAtEntry": false,
50+
"requireExactSource": false,
51+
},
2252
{
2353
// Use IntelliSense to find out which attributes exist for C# debugging
2454
// Use hover for the description of the existing attributes

.vscode/tasks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"group": "build",
1111
"args": [
1212
"build",
13-
"${workspaceFolder}/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj",
13+
"${workspaceFolder}/Microsoft.OpenApi.sln",
1414
"/property:GenerateFullPaths=true",
1515
"/consoleloggerparameters:NoSummary"
1616
],

Microsoft.OpenApi.sln

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.OpenApi.Hidi.Test
3232
EndProject
3333
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerformanceTests", "performance\benchmark\PerformanceTests.csproj", "{537E49E3-325E-40EE-A90E-7556D4D333AA}"
3434
EndProject
35+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{4BB7E3F7-CA7E-45D3-B5AC-5DBB510FD528}"
36+
EndProject
37+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "resultsComparer", "performance\resultsComparer\resultsComparer.csproj", "{5EEA836B-3E08-4BE1-82B8-5236D031B497}"
38+
EndProject
3539
Global
3640
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3741
Debug|Any CPU = Debug|Any CPU
@@ -74,6 +78,10 @@ Global
7478
{537E49E3-325E-40EE-A90E-7556D4D333AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
7579
{537E49E3-325E-40EE-A90E-7556D4D333AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
7680
{537E49E3-325E-40EE-A90E-7556D4D333AA}.Release|Any CPU.Build.0 = Release|Any CPU
81+
{5EEA836B-3E08-4BE1-82B8-5236D031B497}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
82+
{5EEA836B-3E08-4BE1-82B8-5236D031B497}.Debug|Any CPU.Build.0 = Debug|Any CPU
83+
{5EEA836B-3E08-4BE1-82B8-5236D031B497}.Release|Any CPU.ActiveCfg = Release|Any CPU
84+
{5EEA836B-3E08-4BE1-82B8-5236D031B497}.Release|Any CPU.Build.0 = Release|Any CPU
7785
EndGlobalSection
7886
GlobalSection(SolutionProperties) = preSolution
7987
HideSolutionNode = FALSE
@@ -87,6 +95,7 @@ Global
8795
{1D2E0C6E-B103-4CB6-912E-D56FA1501296} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A}
8896
{538936B4-5E14-4EA3-9FD0-F43E2DD014FB} = {E546B92F-20A8-49C3-8323-4B25BB78F3E1}
8997
{6ADC5D41-EDD2-4206-B815-5DFF739C6832} = {6357D7FD-2DE4-4900-ADB9-ABC37052040A}
98+
{5EEA836B-3E08-4BE1-82B8-5236D031B497} = {4BB7E3F7-CA7E-45D3-B5AC-5DBB510FD528}
9099
EndGlobalSection
91100
GlobalSection(ExtensibilityGlobals) = postSolution
92101
SolutionGuid = {9F171EFC-0DB5-4B10-ABFA-AF48D52CC565}

performance/benchmark/Benchmarks.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ namespace performance
1010
public class Benchmarks
1111
{
1212
[Benchmark]
13-
public void EmptyDocument()
13+
public OpenApiDocument EmptyDocument()
1414
{
15-
var document = new OpenApiDocument();
15+
return new OpenApiDocument();
1616
}
1717

1818
[Benchmark]
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// See https://aka.ms/new-console-template for more information
2+
using System.Text.Json;
3+
4+
namespace resultsComparer;
5+
6+
public class Program
7+
{
8+
public static async Task<int> Main(string[] args)
9+
{
10+
var existingBenchmark = await GetBenchmarksAllocatedBytes(ExistingReportPath);
11+
if (existingBenchmark is null)
12+
{
13+
await Console.Error.WriteLineAsync("No existing benchmark data found.");
14+
return 1;
15+
}
16+
var newBenchmark = await GetBenchmarksAllocatedBytes(ExistingReportPath);
17+
if (newBenchmark is null)
18+
{
19+
await Console.Error.WriteLineAsync("No new benchmark data found.");
20+
return 1;
21+
}
22+
IBenchmarkComparisonPolicy[] comparisonPolicies = [
23+
MemoryBenchmarkResultComparer.Instance
24+
];
25+
var hasErrors = false;
26+
foreach(var existingBenchmarkResult in existingBenchmark)
27+
{
28+
if (!newBenchmark.TryGetValue(existingBenchmarkResult.Key, out var newBenchmarkResult))
29+
{
30+
await Console.Error.WriteLineAsync($"No new benchmark result found for {existingBenchmarkResult.Key}.");
31+
hasErrors = true;
32+
}
33+
foreach (var comparisonPolicy in comparisonPolicies)
34+
{
35+
if (!comparisonPolicy.Equals(existingBenchmarkResult.Value, newBenchmarkResult))
36+
{
37+
await Console.Error.WriteLineAsync($"Benchmark result for {existingBenchmarkResult.Key} does not match the existing benchmark result. {comparisonPolicy.GetErrorMessage(existingBenchmarkResult.Value, newBenchmarkResult)}");
38+
hasErrors = true;
39+
}
40+
}
41+
}
42+
43+
if (newBenchmark.Keys.Where(x => !existingBenchmark.ContainsKey(x)).ToArray() is { Length: > 0 } missingKeys)
44+
{
45+
await Console.Error.WriteLineAsync("New benchmark results found that do not exist in the existing benchmark results.");
46+
foreach (var missingKey in missingKeys)
47+
{
48+
await Console.Error.WriteLineAsync($"New benchmark result found: {missingKey}.");
49+
}
50+
hasErrors = true;
51+
}
52+
return hasErrors ? 1 : 0;
53+
}
54+
private const string ExistingReportPath = "../benchmark/BenchmarkDotNet.Artifacts/results/performance.Benchmarks-report.json";
55+
56+
private static async Task<Dictionary<string, BenchmarkResult>?> GetBenchmarksAllocatedBytes(string targetPath, CancellationToken cancellationToken = default)
57+
{
58+
if (!File.Exists(targetPath))
59+
{
60+
return null;
61+
}
62+
using var stream = new FileStream(targetPath, FileMode.Open, FileAccess.Read);
63+
using var document = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken);
64+
var rootElement = document.RootElement;
65+
if (rootElement.ValueKind is not JsonValueKind.Object ||
66+
!rootElement.TryGetProperty("Benchmarks", out var benchmarksNode) ||
67+
benchmarksNode.ValueKind is not JsonValueKind.Array)
68+
{
69+
return null;
70+
}
71+
return benchmarksNode.EnumerateArray().Select(benchmarkNode => {
72+
if (benchmarkNode.ValueKind is not JsonValueKind.Object)
73+
{
74+
return default;
75+
}
76+
if (!benchmarkNode.TryGetProperty("Memory", out var memoryNode) ||
77+
memoryNode.ValueKind is not JsonValueKind.Object ||
78+
!memoryNode.TryGetProperty("BytesAllocatedPerOperation", out var allocatedBytesNode) ||
79+
allocatedBytesNode.ValueKind is not JsonValueKind.Number ||
80+
!allocatedBytesNode.TryGetInt64(out var allocatedBytes))
81+
{
82+
return default;
83+
}
84+
if (!benchmarkNode.TryGetProperty("Method", out var nameNode) ||
85+
nameNode.ValueKind is not JsonValueKind.String ||
86+
nameNode.GetString() is not string name)
87+
{
88+
return default;
89+
}
90+
return (name, new BenchmarkResult(allocatedBytes));
91+
})
92+
.Where(x => x.name is not null && x.Item2 is not null)
93+
.ToDictionary(x => x.name!, x => x.Item2!, StringComparer.OrdinalIgnoreCase);
94+
}
95+
private sealed record BenchmarkResult(long AllocatedBytes);
96+
private interface IBenchmarkComparisonPolicy : IEqualityComparer<BenchmarkResult>
97+
{
98+
string GetErrorMessage(BenchmarkResult? x, BenchmarkResult? y);
99+
}
100+
private sealed class MemoryBenchmarkResultComparer : IBenchmarkComparisonPolicy
101+
{
102+
public static MemoryBenchmarkResultComparer Instance { get; } = new MemoryBenchmarkResultComparer();
103+
public bool Equals(BenchmarkResult? x, BenchmarkResult? y)
104+
{
105+
return x?.AllocatedBytes == y?.AllocatedBytes;
106+
}
107+
108+
public string GetErrorMessage(BenchmarkResult? x, BenchmarkResult? y)
109+
{
110+
return $"Allocated bytes differ: {x?.AllocatedBytes} != {y?.AllocatedBytes}";
111+
}
112+
113+
public int GetHashCode(BenchmarkResult obj)
114+
{
115+
return obj.AllocatedBytes.GetHashCode();
116+
}
117+
}
118+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="system.text.json" Version="9.0.3" />
12+
</ItemGroup>
13+
14+
</Project>

0 commit comments

Comments
 (0)