Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add benchmarks project #3050

Merged
merged 2 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
[Bb]in/
.vs/
.idea*
BenchmarkDotNet.Artifacts*/
coverage
coverage.*
node_modules/
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project>
<ItemGroup>
<PackageVersion Include="Autofac.Extensions.DependencyInjection" Version="4.2.2" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="IdentityServer4" Version="3.1.4" />
Expand Down
10 changes: 10 additions & 0 deletions Swashbuckle.AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
.gitattributes = .gitattributes
.gitignore = .gitignore
benchmark.ps1 = benchmark.ps1
CONTRIBUTING.md = CONTRIBUTING.md
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Expand Down Expand Up @@ -117,6 +118,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalAppWithHostedService
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcWithNullable", "test\WebSites\MvcWithNullable\MvcWithNullable.csproj", "{F88B6070-BE3C-45F9-978C-2ECBA9518C24}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{0C7326F1-F731-4CF9-8A98-80F39541D28F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Swashbuckle.AspNetCore.Benchmarks", "perf\Swashbuckle.AspNetCore.Benchmarks\Swashbuckle.AspNetCore.Benchmarks.csproj", "{28F5840B-AE87-46DB-9D83-C3C8C77C6A13}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -271,6 +276,10 @@ Global
{F88B6070-BE3C-45F9-978C-2ECBA9518C24}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F88B6070-BE3C-45F9-978C-2ECBA9518C24}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F88B6070-BE3C-45F9-978C-2ECBA9518C24}.Release|Any CPU.Build.0 = Release|Any CPU
{28F5840B-AE87-46DB-9D83-C3C8C77C6A13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28F5840B-AE87-46DB-9D83-C3C8C77C6A13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28F5840B-AE87-46DB-9D83-C3C8C77C6A13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28F5840B-AE87-46DB-9D83-C3C8C77C6A13}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -316,6 +325,7 @@ Global
{07BB09CF-6C6F-4D00-A459-93586345C921} = {DB3F57FC-1472-4F03-B551-43394DA3C5EB}
{D06A88E8-6F42-4F40-943A-E266C0AE6EC9} = {DB3F57FC-1472-4F03-B551-43394DA3C5EB}
{F88B6070-BE3C-45F9-978C-2ECBA9518C24} = {DB3F57FC-1472-4F03-B551-43394DA3C5EB}
{28F5840B-AE87-46DB-9D83-C3C8C77C6A13} = {0C7326F1-F731-4CF9-8A98-80F39541D28F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {36FC6A67-247D-4149-8EDD-79FFD1A75F51}
Expand Down
28 changes: 28 additions & 0 deletions benchmark.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#! /usr/bin/env pwsh

#Requires -PSEdition Core
#Requires -Version 7

param(
[Parameter(Mandatory = $false)][string] $Framework = "net8.0",
[Parameter(Mandatory = $false)][string] $Job = ""
)

$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"

$benchmarks = (Join-Path $PSScriptRoot "perf" "Swashbuckle.AspNetCore.Benchmarks" "Swashbuckle.AspNetCore.Benchmarks.csproj")

$additionalArgs = @()

if (-Not [string]::IsNullOrEmpty($Job)) {
$additionalArgs += "--job"
$additionalArgs += $Job
}

if (-Not [string]::IsNullOrEmpty(${env:GITHUB_SHA})) {
$additionalArgs += "--exporters"
$additionalArgs += "json"
}

dotnet run --project $benchmarks --configuration "Release" --framework $Framework -- $additionalArgs --% --filter *
4 changes: 4 additions & 0 deletions perf/Swashbuckle.AspNetCore.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using BenchmarkDotNet.Running;

var switcher = new BenchmarkSwitcher(typeof(Program).Assembly);
switcher.Run(args);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Swashbuckle.AspNetCore.Annotations\Swashbuckle.AspNetCore.Annotations.csproj" />
<ProjectReference Include="..\..\src\Swashbuckle.AspNetCore.SwaggerGen\Swashbuckle.AspNetCore.SwaggerGen.csproj" />
<ProjectReference Include="..\..\src\Swashbuckle.AspNetCore.Swagger\Swashbuckle.AspNetCore.Swagger.csproj" />
<ProjectReference Include="..\..\test\Swashbuckle.AspNetCore.SwaggerGen.Test\Swashbuckle.AspNetCore.SwaggerGen.Test.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
</ItemGroup>
</Project>
155 changes: 155 additions & 0 deletions perf/Swashbuckle.AspNetCore.Benchmarks/XmlCommentsBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.XPath;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerGen.Test;
using Swashbuckle.AspNetCore.TestSupport;

namespace Swashbuckle.AspNetCore.Benchmarks;

[MemoryDiagnoser]
public class XmlCommentsBenchmark
{
private XmlCommentsDocumentFilter _documentFilter;
private OpenApiDocument _document;
private DocumentFilterContext _documentFilterContext;

private XmlCommentsOperationFilter _operationFilter;
private OpenApiOperation _operation;
private OperationFilterContext _operationFilterContext;

private XmlCommentsParameterFilter _parameterFilter;
private OpenApiParameter _parameter;
private ParameterFilterContext _parameterFilterContext;

private XmlCommentsRequestBodyFilter _requestBodyFilter;
private OpenApiRequestBody _requestBody;
private RequestBodyFilterContext _requestBodyFilterContext;

private const int AddMemberCount = 10_000;

[GlobalSetup]
public void Setup()
{
// Load XML
XmlDocument xmlDocument;
using (var xmlComments = File.OpenText($"{typeof(FakeControllerWithXmlComments).Assembly.GetName().Name}.xml"))
{
xmlDocument = new XmlDocument();
xmlDocument.Load(xmlComments);
}

// Append dummy members to XML document
XPathNavigator navigator = xmlDocument.CreateNavigator()!;
navigator.MoveToRoot();
navigator.MoveToChild("doc", string.Empty);
navigator.MoveToChild("members", string.Empty);

for (int i = 0; i < AddMemberCount; i++)
{
navigator.PrependChild(@$"<member name=""benchmark_{i}""></member>");
}

using var xmlStream = new MemoryStream();
xmlDocument.Save(xmlStream);
xmlStream.Seek(0, SeekOrigin.Begin);
var xPathDocument = new XPathDocument(xmlStream);

// Document
_document = new OpenApiDocument();
_documentFilterContext = new DocumentFilterContext(
[
new ApiDescription
{
ActionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(FakeControllerWithXmlComments).GetTypeInfo(),
ControllerName = nameof(FakeControllerWithXmlComments),
},
},
new ApiDescription
{
ActionDescriptor = new ControllerActionDescriptor
{
ControllerTypeInfo = typeof(FakeControllerWithXmlComments).GetTypeInfo(),
ControllerName = nameof(FakeControllerWithXmlComments),
},
},
],
null,
null);

_documentFilter = new XmlCommentsDocumentFilter(xPathDocument);

// Operation
_operation = new OpenApiOperation();
var methodInfo = typeof(FakeConstructedControllerWithXmlComments)
.GetMethod(nameof(FakeConstructedControllerWithXmlComments.ActionWithSummaryAndResponseTags));

var apiDescription = ApiDescriptionFactory.Create(methodInfo: methodInfo, groupName: "v1", httpMethod: "POST", relativePath: "resource");
_operationFilterContext = new OperationFilterContext(apiDescription, null, null, methodInfo);
_operationFilter = new XmlCommentsOperationFilter(xPathDocument);

// Parameter
_parameter = new()
{
Schema = new()
{
Type = "string",
Description = "schema-level description",
},
};

var propertyInfo = typeof(XmlAnnotatedType).GetProperty(nameof(XmlAnnotatedType.StringProperty));
var apiParameterDescription = new ApiParameterDescription();
_parameterFilterContext = new ParameterFilterContext(apiParameterDescription, null, null, propertyInfo: propertyInfo);
_parameterFilter = new XmlCommentsParameterFilter(xPathDocument);

// Request Body
_requestBody = new OpenApiRequestBody
{
Content = new Dictionary<string, OpenApiMediaType>()
{
["application/json"] = new()
{
Schema = new()
{
Type = "string",
},
},
},
};
var parameterInfo = typeof(FakeControllerWithXmlComments)
.GetMethod(nameof(FakeControllerWithXmlComments.ActionWithParamTags))!
.GetParameters()[0];

var bodyParameterDescription = new ApiParameterDescription
{
ParameterDescriptor = new ControllerParameterDescriptor { ParameterInfo = parameterInfo }
};
_requestBodyFilterContext = new RequestBodyFilterContext(bodyParameterDescription, null, null, null);
_requestBodyFilter = new XmlCommentsRequestBodyFilter(xPathDocument);
}

[Benchmark]
public void Document()
=> _documentFilter.Apply(_document, _documentFilterContext);

[Benchmark]
public void Operation()
=> _operationFilter.Apply(_operation, _operationFilterContext);

[Benchmark]
public void Parameter()
=> _parameterFilter.Apply(_parameter, _parameterFilterContext);

[Benchmark]
public void RequestBody()
=> _requestBodyFilter.Apply(_requestBody, _requestBodyFilterContext);
}