From 8d920803318c1d2e6eca70034335c5c9e90bccc3 Mon Sep 17 00:00:00 2001 From: Ivan Maximov Date: Tue, 9 Jan 2024 10:29:04 +0300 Subject: [PATCH] Continuous benchmarking (#152) --- .github/workflows/benchmarks.yml | 45 ++++++++++++++++++++++++++++ README.md | 4 +++ src/Benchmarks/Benchmarks.csproj | 20 +++++++++++++ src/Benchmarks/DeferBenchmarks.cs | 40 +++++++++++++++++++++++++ src/Benchmarks/Program.cs | 11 +++++++ src/Benchmarks/combine-bechmarks.csx | 43 ++++++++++++++++++++++++++ src/SteroidsDI.sln | 7 +++++ 7 files changed, 170 insertions(+) create mode 100644 .github/workflows/benchmarks.yml create mode 100644 src/Benchmarks/Benchmarks.csproj create mode 100644 src/Benchmarks/DeferBenchmarks.cs create mode 100644 src/Benchmarks/Program.cs create mode 100644 src/Benchmarks/combine-bechmarks.csx diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 0000000..a6de5c3 --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,45 @@ +name: Continuous benchmarking +on: + pull_request: + branches: + - master + push: + branches: + - master + +permissions: + contents: write + deployments: write + +jobs: + benchmark: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Run benchmarks + working-directory: src/Benchmarks + run: dotnet run -c Release --exporters json --filter '*' + + - name: Combine benchmarks results + working-directory: src/Benchmarks + run: dotnet tool install -g dotnet-script && dotnet script combine-bechmarks.csx + + - name: Store benchmarks results + uses: benchmark-action/github-action-benchmark@v1 + with: + name: SteroidsDI Benchmarks + tool: 'benchmarkdotnet' + output-file-path: src/Benchmarks/BenchmarkDotNet.Artifacts/results/Combined.Benchmarks.json + github-token: ${{ secrets.GITHUB_TOKEN }} + alert-threshold: '101%' + comment-on-alert: true + fail-on-alert: true + - name: Push benchmarks results + if: github.event_name != 'pull_request' + run: git push 'https://sungam3r:${{ secrets.GITHUB_TOKEN }}@github.com/sungam3r/SteroidsDI.git' gh-pages:gh-pages diff --git a/README.md b/README.md index 81fa5b3..d1d7e77 100644 --- a/README.md +++ b/README.md @@ -430,3 +430,7 @@ await using (new Scoped(scopeFactory)) // Scoped class supports IAsyn Also see [ScopedTestBase](src/SteroidsDI.Tests/Cases/ScopedTestBase.cs) and [ScopedTestDerived](src/SteroidsDI.Tests/Cases/ScopedTestDerived.cs) for more info. This example shows how you can add scope support to all unit tests. + +## Benchmarks + +The results are available [here](https://sungam3r.github.io/SteroidsDI/dev/bench/). diff --git a/src/Benchmarks/Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj new file mode 100644 index 0000000..d35ccdf --- /dev/null +++ b/src/Benchmarks/Benchmarks.csproj @@ -0,0 +1,20 @@ + + + + Exe + net8.0 + enable + enable + $(NoWarn);1591 + + + + + + + + + + + + diff --git a/src/Benchmarks/DeferBenchmarks.cs b/src/Benchmarks/DeferBenchmarks.cs new file mode 100644 index 0000000..ee5792a --- /dev/null +++ b/src/Benchmarks/DeferBenchmarks.cs @@ -0,0 +1,40 @@ +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; +using SteroidsDI; + +namespace Benchmarks; + +public class DeferBenchmarks +{ + private sealed class Dependency + { + } + + private IServiceProvider _provider = null!; + + [GlobalSetup] + public void Setup() + { + var services = new ServiceCollection(); + services + .Configure(opt => opt.AllowRootProviderResolve = true) + .AddSingleton() + .AddDefer(); + + _provider = services.BuildServiceProvider(); + } + + [Benchmark] + public void ResolveDefer() + { + var defer = _provider.GetRequiredService>(); + _ = defer.Value; + } + + [Benchmark] + public void ResolveIDefer() + { + var defer = _provider.GetRequiredService>(); + _ = defer.Value; + } +} diff --git a/src/Benchmarks/Program.cs b/src/Benchmarks/Program.cs new file mode 100644 index 0000000..71b01d7 --- /dev/null +++ b/src/Benchmarks/Program.cs @@ -0,0 +1,11 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Running; +using Benchmarks; + +new DeferBenchmarks().Setup(); +var config = ManualConfig + .Create(DefaultConfig.Instance) + .AddDiagnoser(MemoryDiagnoser.Default); + +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); diff --git a/src/Benchmarks/combine-bechmarks.csx b/src/Benchmarks/combine-bechmarks.csx new file mode 100644 index 0000000..a302c6d --- /dev/null +++ b/src/Benchmarks/combine-bechmarks.csx @@ -0,0 +1,43 @@ +using System.IO; +using System.Linq; +using System.Text.Json.Nodes; + +string resultsDir = "./BenchmarkDotNet.Artifacts/results"; +string resultsFile = "Combined.Benchmarks"; +string searchPattern = "*-report-full-compressed.json"; + +var resultsPath = Path.Combine(resultsDir, resultsFile + ".json"); + +if (!Directory.Exists(resultsDir)) +{ + throw new DirectoryNotFoundException($"Directory not found '{resultsDir}'"); +} + +if (File.Exists(resultsPath)) +{ + File.Delete(resultsPath); +} + +var reports = Directory.GetFiles(resultsDir, searchPattern, SearchOption.TopDirectoryOnly).ToArray(); +if (!reports.Any()) +{ + throw new FileNotFoundException($"Reports not found '{searchPattern}'"); +} + +var combinedReport = JsonNode.Parse(File.ReadAllText(reports.First()))!; +var title = combinedReport["Title"]!; +var benchmarks = combinedReport["Benchmarks"]!.AsArray(); +// Rename title whilst keeping original timestamp +combinedReport["Title"] = $"{resultsFile}{title.GetValue()[^16..]}"; + +foreach (var report in reports.Skip(1)) +{ + var array = JsonNode.Parse(File.ReadAllText(report))!["Benchmarks"]!.AsArray(); + foreach (var benchmark in array) + { + // Double parse avoids "The node already has a parent" exception + benchmarks.Add(JsonNode.Parse(benchmark!.ToJsonString())!); + } +} + +File.WriteAllText(resultsPath, combinedReport.ToString()); diff --git a/src/SteroidsDI.sln b/src/SteroidsDI.sln index 14e0ae2..fcaa946 100644 --- a/src/SteroidsDI.sln +++ b/src/SteroidsDI.sln @@ -40,6 +40,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEM EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{0D490189-8C58-4B71-AB8D-5FBE973D0429}" ProjectSection(SolutionItems) = preProject + ..\.github\workflows\benchmarks.yml = ..\.github\workflows\benchmarks.yml ..\.github\workflows\codeql-analysis.yml = ..\.github\workflows\codeql-analysis.yml ..\.github\workflows\label.yml = ..\.github\workflows\label.yml ..\.github\workflows\publish-preview.yml = ..\.github\workflows\publish-preview.yml @@ -48,6 +49,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ ..\.github\workflows\test.yml = ..\.github\workflows\test.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{3A3F37A4-27BB-4A35-80A3-1973DDE6586B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -74,6 +77,10 @@ Global {AAF4F983-A2CF-4E5F-9862-CAB7EC942850}.Debug|Any CPU.Build.0 = Debug|Any CPU {AAF4F983-A2CF-4E5F-9862-CAB7EC942850}.Release|Any CPU.ActiveCfg = Release|Any CPU {AAF4F983-A2CF-4E5F-9862-CAB7EC942850}.Release|Any CPU.Build.0 = Release|Any CPU + {3A3F37A4-27BB-4A35-80A3-1973DDE6586B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A3F37A4-27BB-4A35-80A3-1973DDE6586B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A3F37A4-27BB-4A35-80A3-1973DDE6586B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A3F37A4-27BB-4A35-80A3-1973DDE6586B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE