diff --git a/.gitignore b/.gitignore index 942ef38c5..582a9e7d5 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ nuget.exe debugSettings.json project.lock.json version.h +.build/ # Profiler result files, just in case they are left lying around :) /.vs/ diff --git a/README.md b/README.md index 63d150550..a9b3ce9bd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Benchmarks for ASP.NET 5 including (but not limited to) scenarios from the [Tech The benchmark repo is set up to work against the latest sources (i.e. not packages from nuget.org) for ASP.NET 5 so make sure you read through the following details to help you get started. -The ASP.NET 5 benchmarks server application itself is in the `./src/Benchmarks` folder. The `./expiremental` folder contains various experimental projects that aren't themselves part of the benchmarks. +The ASP.NET 5 benchmarks server application itself is in the `./src/Benchmarks` folder. The `./experimental` folder contains various experimental projects that aren't themselves part of the benchmarks. ## The scenarios Following are the details of each of the scenarios the server application contains implementations for and thus can be benchmarked: diff --git a/build.cmd b/build.cmd new file mode 100644 index 000000000..61b58f61a --- /dev/null +++ b/build.cmd @@ -0,0 +1,40 @@ +@ECHO off +SETLOCAL + +SET REPO_FOLDER=%~dp0 +CD %REPO_FOLDER% + +SET BUILD_FOLDER=.build +SET KOREBUILD_FOLDER=%BUILD_FOLDER%\KoreBuild-dotnet +SET KOREBUILD_VERSION= + +SET NUGET_PATH=%BUILD_FOLDER%\NuGet.exe +SET NUGET_VERSION=latest +SET CACHED_NUGET=%LocalAppData%\NuGet\nuget.%NUGET_VERSION%.exe + +IF NOT EXIST %BUILD_FOLDER% ( + md %BUILD_FOLDER% +) + +IF NOT EXIST %NUGET_PATH% ( + IF NOT EXIST %CACHED_NUGET% ( + echo Downloading latest version of NuGet.exe... + IF NOT EXIST %LocalAppData%\NuGet ( + md %LocalAppData%\NuGet + ) + @powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/%NUGET_VERSION%/nuget.exe' -OutFile '%CACHED_NUGET%'" + ) + + copy %CACHED_NUGET% %NUGET_PATH% > nul +) + +IF NOT EXIST %KOREBUILD_FOLDER% ( + SET KOREBUILD_DOWNLOAD_ARGS= + IF NOT "%KOREBUILD_VERSION%"=="" ( + SET KOREBUILD_DOWNLOAD_ARGS=-version %KOREBUILD_VERSION% + ) + + %BUILD_FOLDER%\nuget.exe install KoreBuild-dotnet -ExcludeVersion -o %BUILD_FOLDER% -nocache -pre %KOREBUILD_DOWNLOAD_ARGS% +) + +"%KOREBUILD_FOLDER%\build\KoreBuild.cmd" %* diff --git a/makefile.shade b/makefile.shade new file mode 100644 index 000000000..889307dda --- /dev/null +++ b/makefile.shade @@ -0,0 +1,14 @@ +var AUTHORS='Microsoft Open Technologies, Inc.' + +use-standard-lifecycle +k-standard-goals + +#build-compile target='compile' + @{ + var projectFiles = Files + .Include("src/**/project.json") + .Include("experimental/**/project.json") + .ToList(); + + projectFiles.ForEach(projectFile => DotnetBuild(projectFile, E("Configuration"))); + } diff --git a/src/Benchmarks/Controllers/HomeController.cs b/src/Benchmarks/Controllers/HomeController.cs index 7eaccb4a8..83d368226 100644 --- a/src/Benchmarks/Controllers/HomeController.cs +++ b/src/Benchmarks/Controllers/HomeController.cs @@ -1,10 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; using System.Threading.Tasks; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; namespace Benchmarks.Controllers { diff --git a/src/Benchmarks/Data/ApplicationDbContext.cs b/src/Benchmarks/Data/ApplicationDbContext.cs index 52e0e931f..b09cb2f4a 100644 --- a/src/Benchmarks/Data/ApplicationDbContext.cs +++ b/src/Benchmarks/Data/ApplicationDbContext.cs @@ -1,7 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.Data.Entity; +using Microsoft.EntityFrameworkCore; namespace Benchmarks.Data { diff --git a/src/Benchmarks/DebugInfoPageMiddleware.cs b/src/Benchmarks/DebugInfoPageMiddleware.cs index 58b6444f2..5f649d7da 100644 --- a/src/Benchmarks/DebugInfoPageMiddleware.cs +++ b/src/Benchmarks/DebugInfoPageMiddleware.cs @@ -1,12 +1,12 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Hosting; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Features; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.PlatformAbstractions; namespace Benchmarks @@ -35,9 +35,7 @@ public DebugInfoPageMiddleware(RequestDelegate next, IHostingEnvironment hosting public async Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { httpContext.Response.ContentType = "text/html"; diff --git a/src/Benchmarks/ErrorHandlerMiddleware.cs b/src/Benchmarks/ErrorHandlerMiddleware.cs index 117def1ad..004355fb5 100644 --- a/src/Benchmarks/ErrorHandlerMiddleware.cs +++ b/src/Benchmarks/ErrorHandlerMiddleware.cs @@ -1,10 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; namespace Benchmarks { diff --git a/src/Benchmarks/FortunesDapperMiddleware.cs b/src/Benchmarks/FortunesDapperMiddleware.cs index 94d08aa31..5e9dc6b3d 100644 --- a/src/Benchmarks/FortunesDapperMiddleware.cs +++ b/src/Benchmarks/FortunesDapperMiddleware.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -8,8 +8,8 @@ using System.Threading.Tasks; using Benchmarks.Data; using Dapper; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; namespace Benchmarks { @@ -30,9 +30,7 @@ public FortunesDapperMiddleware(RequestDelegate next, string connectionString, D public async Task Invoke(HttpContext httpContext, HtmlEncoder htmlEncoder) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var rows = await LoadRows(_connectionString, _dbProviderFactory); diff --git a/src/Benchmarks/FortunesEfMiddleware.cs b/src/Benchmarks/FortunesEfMiddleware.cs index 51c668d99..9fa155d8e 100644 --- a/src/Benchmarks/FortunesEfMiddleware.cs +++ b/src/Benchmarks/FortunesEfMiddleware.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -6,9 +6,9 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; using Benchmarks.Data; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; -using Microsoft.Data.Entity; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; namespace Benchmarks { @@ -25,9 +25,7 @@ public FortunesEfMiddleware(RequestDelegate next) public async Task Invoke(HttpContext httpContext, HtmlEncoder htmlEncoder) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var db = (ApplicationDbContext)httpContext.RequestServices.GetService(typeof(ApplicationDbContext)); db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; diff --git a/src/Benchmarks/FortunesRawMiddleware.cs b/src/Benchmarks/FortunesRawMiddleware.cs index f30b3636c..e6f90e808 100644 --- a/src/Benchmarks/FortunesRawMiddleware.cs +++ b/src/Benchmarks/FortunesRawMiddleware.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -8,8 +8,8 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; using Benchmarks.Data; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; namespace Benchmarks { @@ -30,9 +30,7 @@ public FortunesRawMiddleware(RequestDelegate next, string connectionString, DbPr public async Task Invoke(HttpContext httpContext, HtmlEncoder htmlEncoder) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var rows = await LoadRows(_connectionString, _dbProviderFactory); diff --git a/src/Benchmarks/JsonMiddleware.cs b/src/Benchmarks/JsonMiddleware.cs index dfa24a9de..9474a474c 100644 --- a/src/Benchmarks/JsonMiddleware.cs +++ b/src/Benchmarks/JsonMiddleware.cs @@ -1,12 +1,12 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Text; using System.Threading.Tasks; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Newtonsoft.Json; namespace Benchmarks @@ -26,9 +26,7 @@ public JsonMiddleware(RequestDelegate next) public Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { httpContext.Response.StatusCode = 200; httpContext.Response.ContentType = "application/json"; diff --git a/src/Benchmarks/Migrations/20151113004227_Initial.Designer.cs b/src/Benchmarks/Migrations/20151113004227_Initial.Designer.cs index bfce825c0..4ea189b25 100644 --- a/src/Benchmarks/Migrations/20151113004227_Initial.Designer.cs +++ b/src/Benchmarks/Migrations/20151113004227_Initial.Designer.cs @@ -1,8 +1,8 @@ using System; -using Microsoft.Data.Entity; -using Microsoft.Data.Entity.Infrastructure; -using Microsoft.Data.Entity.Metadata; -using Microsoft.Data.Entity.Migrations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; using Benchmarks.Data; namespace Benchmarks.Migrations diff --git a/src/Benchmarks/Migrations/20151113004227_Initial.cs b/src/Benchmarks/Migrations/20151113004227_Initial.cs index f880edd6d..63de764aa 100644 --- a/src/Benchmarks/Migrations/20151113004227_Initial.cs +++ b/src/Benchmarks/Migrations/20151113004227_Initial.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using Microsoft.Data.Entity.Migrations; -using Microsoft.Data.Entity.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Metadata; using System.Text; namespace Benchmarks.Migrations diff --git a/src/Benchmarks/Migrations/20151124205054_Fortune.Designer.cs b/src/Benchmarks/Migrations/20151124205054_Fortune.Designer.cs index 0204aa214..0267669cb 100644 --- a/src/Benchmarks/Migrations/20151124205054_Fortune.Designer.cs +++ b/src/Benchmarks/Migrations/20151124205054_Fortune.Designer.cs @@ -1,8 +1,8 @@ using System; -using Microsoft.Data.Entity; -using Microsoft.Data.Entity.Infrastructure; -using Microsoft.Data.Entity.Metadata; -using Microsoft.Data.Entity.Migrations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; using Benchmarks.Data; namespace Benchmarks.Migrations diff --git a/src/Benchmarks/Migrations/20151124205054_Fortune.cs b/src/Benchmarks/Migrations/20151124205054_Fortune.cs index e22e90923..9488d18d0 100644 --- a/src/Benchmarks/Migrations/20151124205054_Fortune.cs +++ b/src/Benchmarks/Migrations/20151124205054_Fortune.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -using Microsoft.Data.Entity.Migrations; -using Microsoft.Data.Entity.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Metadata; namespace Benchmarks.Migrations { diff --git a/src/Benchmarks/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Benchmarks/Migrations/ApplicationDbContextModelSnapshot.cs index cf67310f6..320af52ff 100644 --- a/src/Benchmarks/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Benchmarks/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,8 +1,8 @@ using System; -using Microsoft.Data.Entity; -using Microsoft.Data.Entity.Infrastructure; -using Microsoft.Data.Entity.Metadata; -using Microsoft.Data.Entity.Migrations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; using Benchmarks.Data; namespace Benchmarks.Migrations diff --git a/src/Benchmarks/MultipleQueriesDapperMiddleware.cs b/src/Benchmarks/MultipleQueriesDapperMiddleware.cs index 7f558faa9..fd60a09ab 100644 --- a/src/Benchmarks/MultipleQueriesDapperMiddleware.cs +++ b/src/Benchmarks/MultipleQueriesDapperMiddleware.cs @@ -1,14 +1,13 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Data.Common; -using System.Linq; using System.Threading.Tasks; using Benchmarks.Data; using Dapper; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -36,9 +35,7 @@ public MultipleQueriesDapperMiddleware(RequestDelegate next, string connectionSt public async Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var count = GetQueryCount(httpContext); var rows = await LoadRows(count, _connectionString, _dbProviderFactory); diff --git a/src/Benchmarks/MultipleQueriesEfMiddleware.cs b/src/Benchmarks/MultipleQueriesEfMiddleware.cs index b306a156e..259a4a9fe 100644 --- a/src/Benchmarks/MultipleQueriesEfMiddleware.cs +++ b/src/Benchmarks/MultipleQueriesEfMiddleware.cs @@ -1,12 +1,12 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; using Benchmarks.Data; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; -using Microsoft.Data.Entity; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -29,9 +29,7 @@ public MultipleQueriesEfMiddleware(RequestDelegate next) public async Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var db = (ApplicationDbContext)httpContext.RequestServices.GetService(typeof(ApplicationDbContext)); db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; diff --git a/src/Benchmarks/MultipleQueriesRawMiddleware.cs b/src/Benchmarks/MultipleQueriesRawMiddleware.cs index 5bc4936ba..3fcbdd4a9 100644 --- a/src/Benchmarks/MultipleQueriesRawMiddleware.cs +++ b/src/Benchmarks/MultipleQueriesRawMiddleware.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -6,8 +6,8 @@ using System.Data.Common; using System.Threading.Tasks; using Benchmarks.Data; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -35,9 +35,7 @@ public MultipleQueriesRawMiddleware(RequestDelegate next, string connectionStrin public async Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var count = GetQueryCount(httpContext); var rows = await LoadRows(count, _connectionString, _dbProviderFactory); diff --git a/src/Benchmarks/PlaintextMiddleware.cs b/src/Benchmarks/PlaintextMiddleware.cs index 1302525a6..8d4c594da 100644 --- a/src/Benchmarks/PlaintextMiddleware.cs +++ b/src/Benchmarks/PlaintextMiddleware.cs @@ -1,11 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Text; using System.Threading.Tasks; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; namespace Benchmarks { @@ -23,9 +23,7 @@ public PlaintextMiddleware(RequestDelegate next) public Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { httpContext.Response.StatusCode = 200; httpContext.Response.ContentType = "text/plain"; diff --git a/src/Benchmarks/Program.cs b/src/Benchmarks/Program.cs index e2fd9d803..f39cb9e27 100644 --- a/src/Benchmarks/Program.cs +++ b/src/Benchmarks/Program.cs @@ -1,11 +1,10 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Threading; -using Microsoft.AspNet.Hosting; -using Microsoft.AspNet.Http.Features; -using Microsoft.AspNet.Server.Features; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -13,83 +12,139 @@ namespace Benchmarks { public class Program { + public static string[] Args; + public static void Main(string[] args) { - var hostingConfig = new ConfigurationBuilder() - .AddJsonFile("hosting.json", optional: true) - .AddEnvironmentVariables() - .AddEnvironmentVariables(prefix: "ASPNET_") - .AddCommandLine(args) + Args = args; + + Console.WriteLine(); + Console.WriteLine("ASP.NET Core Benchmarks"); + Console.WriteLine("-----------------------"); + + var scenarios = LoadScenarios(args); + + StartInteractiveConsoleThread(); + + var webHost = new WebHostBuilder() + .UseServer("Microsoft.AspNetCore.Server.Kestrel") + .UseCaptureStartupErrors(false) + .UseDefaultConfiguration(args) + .UseStartup() + .ConfigureServices(services => services.AddSingleton(scenarios)) .Build(); - var hostBuilder = new WebHostBuilder(hostingConfig, captureStartupErrors: true); - hostBuilder.UseStartup(typeof(Startup)); + webHost.Run(); + } - var host = hostBuilder.Build(); + private static Scenarios LoadScenarios(string[] args) + { + var scenarioConfig = new ConfigurationBuilder() + .AddJsonFile("scenarios.json", optional: true) + .AddCommandLine(args) + .Build() + .GetChildren() + .ToList(); - using (var app = host.Start()) - { - // Echo out the addresses we're listening on - var hostingEnv = app.Services.GetRequiredService(); - Console.WriteLine("Hosting environment: " + hostingEnv.EnvironmentName); + var scenarios = new Scenarios(); - var serverAddresses = app.ServerFeatures.Get(); - if (serverAddresses != null) + if (scenarioConfig.Count > 0) + { + Console.WriteLine("Scenario configuration found in scenarios.json and/or command line args"); + foreach (var scenario in scenarioConfig) { - foreach (var address in serverAddresses.Addresses) - { - Console.WriteLine("Now listening on: " + address); - } + scenarios.Enable(scenario.Value); } + } + else + { + Console.WriteLine("Which scenarios would you like to enable?:"); + Console.WriteLine(); + foreach (var scenario in scenarios.GetNames()) + { + Console.WriteLine(" " + scenario); + } + Console.WriteLine(); + Console.WriteLine("Type full or partial scenario names separated by commas and hit [Enter]"); + Console.Write("> "); - Console.WriteLine("Application started. Press Ctrl+C to shut down."); - - var appLifetime = app.Services.GetRequiredService(); + var choices = Console.ReadLine().Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries); - // Run the interaction on a separate thread as we don't have Console.KeyAvailable on .NET Core so can't - // do a pre-emptive check before we call Console.ReadKey (which blocks, hard) - var interactiveThread = new Thread(() => + if (choices.Length == 0) { Console.WriteLine(); - Console.WriteLine("Press 'C' to force GC or any other key to display GC stats"); + Console.WriteLine("No choice entered, enabling default scenarios"); + scenarios.EnableDefault(); + } + else + { + var count = 0; - while (true) + foreach (var choice in choices) { - var key = Console.ReadKey(intercept: true); - - if (key.Key == ConsoleKey.C) - { - Console.WriteLine(); - Console.Write("Forcing GC..."); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - Console.WriteLine(" done!"); - } - else - { - Console.WriteLine(); - Console.WriteLine($"Allocated: {GetAllocatedMemory()}"); - Console.WriteLine($"Gen 0: {GC.CollectionCount(0)}, Gen 1: {GC.CollectionCount(1)}, Gen 2: {GC.CollectionCount(2)}"); - } + count += scenarios.Enable(choice); } - }); - // Handle Ctrl+C in order to gracefully shutdown the web server - Console.CancelKeyPress += (sender, eventArgs) => - { - Console.WriteLine(); - Console.WriteLine("Shutting down application..."); + if (count == 0) + { + Console.WriteLine(); + Console.WriteLine("No matching scenarios found, enabling defaults"); + scenarios.EnableDefault(); + } + } + } - appLifetime.StopApplication(); + Console.WriteLine(); + Console.WriteLine("The following scenarios were enabled:"); + foreach (var scenario in scenarios.GetEnabled()) + { + Console.WriteLine(" " + scenario); + } + Console.WriteLine(); + + return scenarios; + } - eventArgs.Cancel = true; - }; + private static void StartInteractiveConsoleThread() + { + // Run the interaction on a separate thread as we don't have Console.KeyAvailable on .NET Core so can't + // do a pre-emptive check before we call Console.ReadKey (which blocks, hard) + + var started = new ManualResetEvent(false); + + var interactiveThread = new Thread(() => + { + Console.WriteLine("Press 'C' to force GC or any other key to display GC stats"); + Console.WriteLine(); - interactiveThread.Start(); + started.Set(); - appLifetime.ApplicationStopping.WaitHandle.WaitOne(); - } + while (true) + { + var key = Console.ReadKey(intercept: true); + + if (key.Key == ConsoleKey.C) + { + Console.WriteLine(); + Console.Write("Forcing GC..."); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + Console.WriteLine(" done!"); + } + else + { + Console.WriteLine(); + Console.WriteLine($"Allocated: {GetAllocatedMemory()}"); + Console.WriteLine($"Gen 0: {GC.CollectionCount(0)}, Gen 1: {GC.CollectionCount(1)}, Gen 2: {GC.CollectionCount(2)}"); + } + } + }); + + interactiveThread.IsBackground = true; + interactiveThread.Start(); + + started.WaitOne(); } private static string GetAllocatedMemory(bool forceFullCollection = false) diff --git a/src/Benchmarks/Scenarios.cs b/src/Benchmarks/Scenarios.cs new file mode 100644 index 000000000..87478d19d --- /dev/null +++ b/src/Benchmarks/Scenarios.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; + +namespace Benchmarks +{ + public class Scenarios + { + public bool Plaintext { get; set; } + + public bool Json { get; set; } + + public bool StaticFiles { get; set; } + + public bool MvcApis { get; set; } + + public bool MvcViews { get; set; } + + public bool DbSingleQueryRaw { get; set; } + + public bool DbSingleQueryEf { get; set; } + + public bool DbSingleQueryDapper { get; set; } + + public bool DbMultiQueryRaw { get; set; } + + public bool DbMultiQueryEf { get; set; } + + public bool DbMultiQueryDapper { get; set; } + + public bool DbFortunesRaw { get; set; } + + public bool DbFortunesEf { get; set; } + + public bool DbFortunesDapper { get; set; } + + public bool Any(string partialName) => + typeof(Scenarios).GetTypeInfo().DeclaredProperties + .Where(p => p.Name.IndexOf(partialName, StringComparison.Ordinal) >= 0 && (bool)p.GetValue(this)) + .Any(); + + public IEnumerable GetNames() => + typeof(Scenarios).GetTypeInfo().DeclaredProperties + .Select(p => p.Name); + + public IEnumerable GetEnabled() => + typeof(Scenarios).GetTypeInfo().DeclaredProperties + .Where(p => p.GetValue(this) is bool && (bool)p.GetValue(this)) + .Select(p => p.Name); + + public int Enable(string partialName) + { + var props = typeof(Scenarios).GetTypeInfo().DeclaredProperties + .Where(p => string.Equals(partialName, "[all]", StringComparison.OrdinalIgnoreCase) || p.Name.IndexOf(partialName, StringComparison.OrdinalIgnoreCase) >= 0) + .ToList(); + + foreach (var p in props) + { + p.SetValue(this, true); + } + + return props.Count; + } + + public void EnableDefault() + { + Plaintext = true; + Json = true; + } + } +} \ No newline at end of file diff --git a/src/Benchmarks/SingleQueryDapperMiddleware.cs b/src/Benchmarks/SingleQueryDapperMiddleware.cs index dff57ef44..6681fd1a8 100644 --- a/src/Benchmarks/SingleQueryDapperMiddleware.cs +++ b/src/Benchmarks/SingleQueryDapperMiddleware.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -7,8 +7,8 @@ using System.Threading.Tasks; using Benchmarks.Data; using Dapper; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -36,9 +36,7 @@ public SingleQueryDapperMiddleware(RequestDelegate next, string connectionString public async Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var row = await LoadRow(_connectionString, _dbProviderFactory); diff --git a/src/Benchmarks/SingleQueryEfMiddleware.cs b/src/Benchmarks/SingleQueryEfMiddleware.cs index 394e6963c..e20f69976 100644 --- a/src/Benchmarks/SingleQueryEfMiddleware.cs +++ b/src/Benchmarks/SingleQueryEfMiddleware.cs @@ -1,12 +1,12 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Threading.Tasks; using Benchmarks.Data; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; -using Microsoft.Data.Entity; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -29,9 +29,7 @@ public SingleQueryEfMiddleware(RequestDelegate next) public async Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var db = (ApplicationDbContext)httpContext.RequestServices.GetService(typeof(ApplicationDbContext)); diff --git a/src/Benchmarks/SingleQueryRawMiddleware.cs b/src/Benchmarks/SingleQueryRawMiddleware.cs index cd5b51fb7..588933c15 100644 --- a/src/Benchmarks/SingleQueryRawMiddleware.cs +++ b/src/Benchmarks/SingleQueryRawMiddleware.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -6,8 +6,8 @@ using System.Data.Common; using System.Threading.Tasks; using Benchmarks.Data; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -35,9 +35,7 @@ public SingleQueryRawMiddleware(RequestDelegate next, string connectionString, D public async Task Invoke(HttpContext httpContext) { - // We check Ordinal explicitly first because it's faster than OrdinalIgnoreCase - if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal) || - httpContext.Request.Path.StartsWithSegments(_path, StringComparison.OrdinalIgnoreCase)) + if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal)) { var row = await LoadRow(_connectionString, _dbProviderFactory); diff --git a/src/Benchmarks/Startup.cs b/src/Benchmarks/Startup.cs index aa2f288bc..74272997f 100644 --- a/src/Benchmarks/Startup.cs +++ b/src/Benchmarks/Startup.cs @@ -1,14 +1,17 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using Benchmarks.Data; -using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Hosting; -using Microsoft.AspNet.Http; -using Microsoft.Data.Entity; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Http.Internal; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -16,94 +19,144 @@ namespace Benchmarks { public class Startup { - public Startup(IHostingEnvironment env) + public Startup(IHostingEnvironment env, Scenarios scenarios) { // Set up configuration sources. var builder = new ConfigurationBuilder() + .Include(env.Configuration) .AddJsonFile("appsettings.json") .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); - if (env.Configuration != null) - { - // This allows passing of config values via the cmd line, e.g.: dnx web --app.EnableDbTests=true - builder.AddConfiguration("app.", env.Configuration); - } - Configuration = builder.Build(); Configuration.Bind(StartupOptions); + + Scenarios = scenarios ?? new Scenarios(); } public IConfigurationRoot Configuration { get; set; } public Options StartupOptions { get; } = new Options(); + + public Scenarios Scenarios { get; } public void ConfigureServices(IServiceCollection services) { // No scenarios covered by the benchmarks require the HttpContextAccessor so we're replacing it with a // no-op version to avoid the cost. services.AddSingleton(typeof(IHttpContextAccessor), typeof(InertHttpContextAccessor)); + services.AddSingleton(typeof(IHttpContextFactory), typeof(PooledContextFactory)); - if (StartupOptions.EnableDbTests) + if (Scenarios.Any("Db")) { services.AddSingleton(); - // TODO: Add support for plugging in different DbProviderFactory implementations via configuration services.AddSingleton(SqlClientFactory.Instance); + } + if (Scenarios.Any("Ef")) + { services.AddEntityFramework() .AddSqlServer() .AddDbContext(options => options.UseSqlServer(StartupOptions.ConnectionString)); + } + + if (Scenarios.Any("Fortunes")) + { services.AddWebEncoders(); } - services.AddMvc(); + if (Scenarios.Any("Mvc")) + { + var mvcBuilder = services.AddMvcCore(); + + if (Scenarios.MvcViews) + { + mvcBuilder + .AddViews() + .AddRazorViewEngine(); + } + } } public void Configure(IApplicationBuilder app) { app.UseErrorHandler(); - app.UsePlainText(); - app.UseJson(); - if (StartupOptions.EnableDbTests) + if (Scenarios.Plaintext) + { + app.UsePlainText(); + } + + if (Scenarios.Json) + { + app.UseJson(); + } + + // Single query endpoints + if (Scenarios.DbSingleQueryRaw) { app.UseSingleQueryRaw(StartupOptions.ConnectionString); + } + + if (Scenarios.DbSingleQueryDapper) + { app.UseSingleQueryDapper(StartupOptions.ConnectionString); + } + + if (Scenarios.DbSingleQueryEf) + { app.UseSingleQueryEf(); + } + // Multiple query endpoints + if (Scenarios.DbMultiQueryRaw) + { app.UseMultipleQueriesRaw(StartupOptions.ConnectionString); + } + + if (Scenarios.DbMultiQueryDapper) + { app.UseMultipleQueriesDapper(StartupOptions.ConnectionString); - app.UseMultipleQueriesEf(); + } - app.UseFortunesRaw(StartupOptions.ConnectionString); - app.UseFortunesDapper(StartupOptions.ConnectionString); - app.UseFortunesEf(); + if (Scenarios.DbMultiQueryEf) + { + app.UseMultipleQueriesEf(); + } + if (Scenarios.Any("Db")) + { var dbContext = (ApplicationDbContext)app.ApplicationServices.GetService(typeof(ApplicationDbContext)); var seeder = (ApplicationDbSeeder)app.ApplicationServices.GetService(typeof(ApplicationDbSeeder)); if (!seeder.Seed(dbContext)) { Environment.Exit(1); } - Console.WriteLine("Database tests enabled"); } - app.UseMvc(); + if (Scenarios.Any("Mvc")) + { + app.UseMvc(); + } - if (StartupOptions.EnableStaticFileTests) + if (Scenarios.StaticFiles) { app.UseStaticFiles(); - Console.WriteLine("Static file tests enabled"); } app.UseDebugInfoPage(); - app.Run(context => context.Response.WriteAsync("Try /plaintext instead")); + app.Run(context => context.Response.WriteAsync("Try /plaintext instead, or /debug for more information")); } - + + public class Options + { + public string ConnectionString { get; set; } + } + public class InertHttpContextAccessor : IHttpContextAccessor { public HttpContext HttpContext @@ -113,13 +166,68 @@ public HttpContext HttpContext } } - public class Options + public class PooledContextFactory : IHttpContextFactory { - public bool EnableDbTests { get; set; } + private IHttpContextAccessor _httpContextAccessor; - public bool EnableStaticFileTests { get; set; } + [ThreadStatic] + static Queue _contextPool; - public string ConnectionString { get; set; } + public PooledContextFactory() : this(httpContextAccessor: null) + { + } + + public PooledContextFactory(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + private Queue ContextPool + { + get + { + if (_contextPool == null) + { + _contextPool = new Queue(16); + } + + return _contextPool; + } + } + + public HttpContext Create(IFeatureCollection featureCollection) + { + var contextPool = ContextPool; + if (contextPool.Count > 0) + { + var context = contextPool.Dequeue(); + context.Initialize(featureCollection); + return context; + } + + return new DefaultHttpContext(featureCollection); + } + + public void Dispose(HttpContext httpContext) + { + if (_httpContextAccessor != null) + { + _httpContextAccessor.HttpContext = null; + } + + var context = httpContext as DefaultHttpContext; + + if (context != null) + { + context.Uninitialize(); + + var contextPool = ContextPool; + if (contextPool.Count < 16) + { + contextPool.Enqueue(context); + } + } + } } } } diff --git a/src/Benchmarks/appsettings.json b/src/Benchmarks/appsettings.json index 7456d9719..ae0375d8f 100644 --- a/src/Benchmarks/appsettings.json +++ b/src/Benchmarks/appsettings.json @@ -1,3 +1,4 @@ { - "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-Benchmarks;Trusted_Connection=True;MultipleActiveResultSets=true" + "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet5-Benchmarks;Trusted_Connection=True;MultipleActiveResultSets=true", + "Scenarios": "Plaintext,Json" } diff --git a/src/Benchmarks/hosting.json b/src/Benchmarks/hosting.json deleted file mode 100644 index 765930471..000000000 --- a/src/Benchmarks/hosting.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "server": "Microsoft.AspNet.Server.Kestrel", - "server.urls": "http://*:5001" -} diff --git a/src/Benchmarks/project.json b/src/Benchmarks/project.json index 7f2664858..6bdec0abe 100644 --- a/src/Benchmarks/project.json +++ b/src/Benchmarks/project.json @@ -1,33 +1,31 @@ { "webroot": "wwwroot", "version": "1.0.0-*", - "compilationOptions": { "emitEntryPoint": true }, - "dependencies": { "Dapper": "1.50.0-*", - "EntityFramework.Commands": "7.0.0-*", - "EntityFramework.MicrosoftSqlServer": "7.0.0-*", - "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Server.Kestrel": "1.0.0-*", - "Microsoft.AspNet.Server.WebListener": "1.0.0-*", - "Microsoft.AspNet.StaticFiles": "1.0.0-*", - "Microsoft.Extensions.WebEncoders": "1.0.0-*", - "Newtonsoft.Json": "7.0.1" + "Microsoft.EntityFrameworkCore.Commands": "1.0.0-*", + "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0-*", + "Microsoft.AspNetCore.Mvc": "1.0.0-*", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*", + "Microsoft.AspNetCore.Server.WebListener": "0.1.0-*", + "Microsoft.AspNetCore.StaticFiles": "1.0.0-*", + "Microsoft.Extensions.Configuration.Binder": "1.0.0-*" }, - "commands": { "Benchmarks": "Benchmarks", - "ef": "EntityFramework.Commands" + "ef": "Microsoft.EntityFrameworkCore.Commands" }, - "frameworks": { - "dnx451": { }, - "dnxcore50": { } + "net451": {}, + "dnxcore50": { + "dependencies": { + "System.Runtime.Serialization.Primitives": "4.1.0-*" + } + } }, - "publishExclude": [ "node_modules", "bower_components", @@ -40,4 +38,4 @@ "node_modules", "bower_components" ] -} +} \ No newline at end of file