diff --git a/AspNetCore.sln b/AspNetCore.sln index d81f7566e422..989394fa94e1 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1455,6 +1455,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropClient", "src\Grpc\t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropWebsite", "src\Grpc\test\testassets\InteropWebsite\InteropWebsite.csproj", "{19189670-E206-471D-94F8-7D3D545E5020}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.ConsoleHost", "src\Components\benchmarkapps\Wasm.Performance\ConsoleHost\Wasm.Performance.ConsoleHost.csproj", "{E9408723-E6A9-4715-B906-3B25B0238ABA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -6889,6 +6891,18 @@ Global {19189670-E206-471D-94F8-7D3D545E5020}.Release|x64.Build.0 = Release|Any CPU {19189670-E206-471D-94F8-7D3D545E5020}.Release|x86.ActiveCfg = Release|Any CPU {19189670-E206-471D-94F8-7D3D545E5020}.Release|x86.Build.0 = Release|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x64.Build.0 = Debug|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x86.ActiveCfg = Debug|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Debug|x86.Build.0 = Debug|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|Any CPU.Build.0 = Release|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x64.ActiveCfg = Release|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x64.Build.0 = Release|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x86.ActiveCfg = Release|Any CPU + {E9408723-E6A9-4715-B906-3B25B0238ABA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -7618,6 +7632,7 @@ Global {00B2DD87-7E2A-4460-BE1B-5E18B1062B7F} = {E763DA15-8F4E-446C-99B8-309053C75598} {C3A0F425-669F-46A8-893F-CF449A6DAE56} = {00B2DD87-7E2A-4460-BE1B-5E18B1062B7F} {19189670-E206-471D-94F8-7D3D545E5020} = {00B2DD87-7E2A-4460-BE1B-5E18B1062B7F} + {E9408723-E6A9-4715-B906-3B25B0238ABA} = {6276A9A0-791B-49C1-AD8F-50AC47CDC196} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F} diff --git a/src/Components/ComponentsNoDeps.slnf b/src/Components/ComponentsNoDeps.slnf index dd08970d02a7..e9e921484d1e 100644 --- a/src/Components/ComponentsNoDeps.slnf +++ b/src/Components/ComponentsNoDeps.slnf @@ -37,6 +37,7 @@ "src\\Components\\WebAssembly\\Sdk\\integrationtests\\Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj", "src\\Components\\Web\\src\\Microsoft.AspNetCore.Components.Web.csproj", "src\\Components\\Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj", + "src\\Components\\benchmarkapps\\Wasm.Performance\\ConsoleHost\\Wasm.Performance.ConsoleHost.csproj", "src\\Components\\benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj", "src\\Components\\benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj", "src\\Components\\test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj", diff --git a/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/ConsoleHostRenderer.cs b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/ConsoleHostRenderer.cs new file mode 100644 index 000000000000..f29a516b1aa7 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/ConsoleHostRenderer.cs @@ -0,0 +1,41 @@ +// 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.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.Extensions.Logging; + +namespace Wasm.Performance.ConsoleHost +{ + internal class ConsoleHostRenderer : Renderer + { + public ConsoleHostRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory) + : base(serviceProvider, loggerFactory) + { + } + + public override Dispatcher Dispatcher { get; } = new NullDispatcher(); + + protected override void HandleException(Exception exception) + { + ExceptionDispatchInfo.Capture(exception).Throw(); + } + + protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) + { + // ConsoleHost is only for profiling the .NET side of execution. + // There isn't a real display to update. + return Task.CompletedTask; + } + + // Expose some protected APIs publicly + public new int AssignRootComponentId(IComponent component) + => base.AssignRootComponentId(component); + + public new Task RenderRootComponentAsync(int componentId) + => base.RenderRootComponentAsync(componentId); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/NullDispatcher.cs b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/NullDispatcher.cs new file mode 100644 index 000000000000..bf600ab8f0d5 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/NullDispatcher.cs @@ -0,0 +1,30 @@ +// 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.AspNetCore.Components; + +namespace Wasm.Performance.ConsoleHost +{ + internal class NullDispatcher : Dispatcher + { + public override bool CheckAccess() + => true; + + public override Task InvokeAsync(Action workItem) + { + workItem(); + return Task.CompletedTask; + } + + public override Task InvokeAsync(Func workItem) + => workItem(); + + public override Task InvokeAsync(Func workItem) + => Task.FromResult(workItem()); + + public override Task InvokeAsync(Func> workItem) + => workItem(); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Program.cs new file mode 100644 index 000000000000..c32f16eb3a30 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Program.cs @@ -0,0 +1,27 @@ +// 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.Extensions.CommandLineUtils; +using Wasm.Performance.ConsoleHost.Scenarios; + +namespace Wasm.Performance.ConsoleHost +{ + internal class Program : CommandLineApplication + { + static void Main(string[] args) + { + new Program().Execute(args); + } + + public Program() + { + OnExecute(() => + { + ShowHelp(); + return 1; + }); + + Commands.Add(new GridScenario()); + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Scenarios/ComponentRenderingScenarioBase.cs b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Scenarios/ComponentRenderingScenarioBase.cs new file mode 100644 index 000000000000..bf5cbc5d833c --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Scenarios/ComponentRenderingScenarioBase.cs @@ -0,0 +1,50 @@ +// 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.Extensions.CommandLineUtils; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Wasm.Performance.ConsoleHost.Scenarios +{ + internal abstract class ComponentRenderingScenarioBase : CommandLineApplication + { + protected ComponentRenderingScenarioBase(string name) + { + Name = name; + + var cyclesOption = new CommandOption("--cycles", CommandOptionType.SingleValue); + Options.Add(cyclesOption); + + OnExecute(() => + { + var numCycles = cyclesOption.HasValue() ? int.Parse(cyclesOption.Value()) : 1; + + var serviceCollection = new ServiceCollection(); + PopulateServiceCollection(serviceCollection); + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var loggerFactory = serviceProvider.GetRequiredService(); + var renderer = new ConsoleHostRenderer(serviceProvider, loggerFactory); + + var startTime = DateTime.Now; + ExecuteAsync(renderer, numCycles).Wait(); + + var duration = DateTime.Now - startTime; + var durationPerCycle = (duration / numCycles).TotalMilliseconds; + Console.WriteLine($"{Name}: {durationPerCycle:F1}ms per cycle (cycles tested: {numCycles})"); + + return 0; + }); + } + + protected virtual void PopulateServiceCollection(IServiceCollection serviceCollection) + { + serviceCollection.AddLogging(); + } + + protected abstract Task ExecuteAsync(ConsoleHostRenderer renderer, int numCycles); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Scenarios/GridScenario.cs b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Scenarios/GridScenario.cs new file mode 100644 index 000000000000..b40f09b96a18 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Scenarios/GridScenario.cs @@ -0,0 +1,39 @@ +// 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.Extensions.CommandLineUtils; +using Wasm.Performance.TestApp.Pages; + +namespace Wasm.Performance.ConsoleHost.Scenarios +{ + internal class GridScenario : ComponentRenderingScenarioBase + { + readonly CommandOption _gridTypeOption = new CommandOption("--gridtype", CommandOptionType.SingleValue); + + public GridScenario() : base("grid") + { + Options.Add(_gridTypeOption); + } + + protected override async Task ExecuteAsync(ConsoleHostRenderer renderer, int numCycles) + { + var gridType = _gridTypeOption.HasValue() + ? (GridRendering.RenderMode)Enum.Parse(typeof(GridRendering.RenderMode), _gridTypeOption.Value(), true) + : GridRendering.RenderMode.FastGrid; + + for (var i = 0; i < numCycles; i++) + { + var hostPage = new GridRendering { SelectedRenderMode = gridType }; + hostPage.Show(); + + var componentId = renderer.AssignRootComponentId(hostPage); + await renderer.RenderRootComponentAsync(componentId); + + hostPage.ChangePage(); + await renderer.RenderRootComponentAsync(componentId); + } + } + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Wasm.Performance.ConsoleHost.csproj b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Wasm.Performance.ConsoleHost.csproj new file mode 100644 index 000000000000..e92ce06f160b --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Wasm.Performance.ConsoleHost.csproj @@ -0,0 +1,15 @@ + + + + Exe + $(DefaultNetCoreTargetFramework) + false + false + + + + + + + + diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs index 81cd361dce7c..769f99eda4d3 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs @@ -9,7 +9,9 @@ public static class BenchmarkEvent { public static void Send(IJSRuntime jsRuntime, string name) { - ((IJSInProcessRuntime)jsRuntime).Invoke( + // jsRuntime will be null if we're in an environment without any + // JS runtime, e.g., the console runner + ((IJSInProcessRuntime)jsRuntime)?.Invoke( "receiveBenchmarkEvent", name); } diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor index 4fa366f44c79..c7dc8137081e 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/GridRendering.razor @@ -1,11 +1,11 @@ -@page "/gridrendering" +@page "/gridrendering" @inject IJSRuntime JSRuntime @using Wasm.Performance.TestApp.Shared.FastGrid

20 x 200 Grid

- @@ -23,7 +23,7 @@ {

(No data assigned)

} -else if (selectedRenderMode == RenderMode.FastGrid) +else if (SelectedRenderMode == RenderMode.FastGrid) {

FastGrid represents a minimal, optimized implementation of a grid.

@@ -50,13 +50,13 @@ else if (selectedRenderMode == RenderMode.FastGrid) @context.Summary } -else if (selectedRenderMode == RenderMode.PlainTable) +else if (SelectedRenderMode == RenderMode.PlainTable) {

PlainTable represents a minimal but not optimized implementation of a grid.

} -else if (selectedRenderMode == RenderMode.ComplexTable) +else if (SelectedRenderMode == RenderMode.ComplexTable) {

ComplexTable represents a maximal, not optimized implementation of a grid, using a wide range of Blazor features at once.

@@ -64,9 +64,9 @@ else if (selectedRenderMode == RenderMode.ComplexTable) } @code { - enum RenderMode { PlainTable, ComplexTable, FastGrid } + public enum RenderMode { PlainTable, ComplexTable, FastGrid } - private RenderMode selectedRenderMode = RenderMode.FastGrid; + public RenderMode SelectedRenderMode { get; set; } = RenderMode.FastGrid; private WeatherForecast[] forecasts; public List Columns { get; set; } = new List @@ -89,17 +89,17 @@ else if (selectedRenderMode == RenderMode.ComplexTable) TemperatureC = index, }; - void Show() + public void Show() { forecasts = staticSampleDataPage1; } - void Hide() + public void Hide() { forecasts = null; } - void ChangePage() + public void ChangePage() { forecasts = (forecasts == staticSampleDataPage1) ? staticSampleDataPage2 : staticSampleDataPage1; } diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor index 70c95ec905d3..eccb07f4dc1b 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor @@ -1,4 +1,4 @@ -@using WeatherForecast = Pages.GridRendering.WeatherForecast +@using WeatherForecast = Pages.GridRendering.WeatherForecast @@ -13,7 +13,7 @@ + OnClick="@HandleClickEvent">
@@ -26,21 +26,8 @@ [Parameter] public List Columns { get; set; } - DateTime t1; - DateTime t2; - Task RefreshComponent(int index) + Task HandleClickEvent(int index) { - t1 = DateTime.Now; - StateHasChanged(); return Task.CompletedTask; } - protected override Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender) - { - t2 = DateTime.Now; - Console.WriteLine("Refresh Time " + (t2 - t1).TotalMilliseconds); - } - return base.OnAfterRenderAsync(firstRender); - } } diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor index ea75beebc734..e22bab77b9b6 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor @@ -1,4 +1,4 @@ -@using WeatherForecast = Pages.GridRendering.WeatherForecast +@using WeatherForecast = Pages.GridRendering.WeatherForecast @@ -12,7 +12,7 @@ + OnClick="@HandleClickEvent">
@@ -24,21 +24,8 @@ [Parameter] public List Columns { get; set; } - DateTime t1; - DateTime t2; - Task RefreshComponent(int index) + Task HandleClickEvent(int index) { - t1 = DateTime.Now; - StateHasChanged(); return Task.CompletedTask; } - protected override Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender) - { - t2 = DateTime.Now; - Console.WriteLine("Refresh Time " + (t2 - t1).TotalMilliseconds); - } - return base.OnAfterRenderAsync(firstRender); - } }