Browse Source

Add console runner for measuring Blazor perf on desktop interpreter (#24469)

Steve Sanderson 5 years ago
parent
commit
1885af9634

+ 15 - 0
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}

+ 1 - 0
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",

+ 41 - 0
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);
+    }
+}

+ 30 - 0
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<Task> workItem)
+            => workItem();
+
+        public override Task<TResult> InvokeAsync<TResult>(Func<TResult> workItem)
+            => Task.FromResult(workItem());
+
+        public override Task<TResult> InvokeAsync<TResult>(Func<Task<TResult>> workItem)
+            => workItem();
+    }
+}

+ 27 - 0
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());
+        }
+    }
+}

+ 50 - 0
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<ILoggerFactory>();
+                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);
+    }
+}

+ 39 - 0
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);
+            }
+        }
+    }
+}

+ 15 - 0
src/Components/benchmarkapps/Wasm.Performance/ConsoleHost/Wasm.Performance.ConsoleHost.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
+    <IsPackable>false</IsPackable>
+    <IsShipping>false</IsShipping>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" />
+    <Compile Include="$(SharedSourceRoot)CommandLineUtils\**\*.cs" />
+  </ItemGroup>
+
+</Project>

+ 3 - 1
src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs

@@ -9,7 +9,9 @@ namespace Wasm.Performance.TestApp
     {
         public static void Send(IJSRuntime jsRuntime, string name)
         {
-            ((IJSInProcessRuntime)jsRuntime).Invoke<object>(
+            // jsRuntime will be null if we're in an environment without any
+            // JS runtime, e.g., the console runner
+            ((IJSInProcessRuntime)jsRuntime)?.Invoke<object>(
                 "receiveBenchmarkEvent",
                 name);
         }

+ 10 - 10
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
 
 <h1>20 x 200 Grid</h1>
 
 <fieldset>
-    <select id="render-mode" @bind="selectedRenderMode">
+    <select id="render-mode" @bind="SelectedRenderMode">
         <option>@RenderMode.FastGrid</option>
         <option>@RenderMode.PlainTable</option>
         <option>@RenderMode.ComplexTable</option>
@@ -23,7 +23,7 @@
 {
     <p><em>(No data assigned)</em></p>
 }
-else if (selectedRenderMode == RenderMode.FastGrid)
+else if (SelectedRenderMode == RenderMode.FastGrid)
 {
     <p>FastGrid represents a minimal, optimized implementation of a grid.</p>
 
@@ -50,13 +50,13 @@ else if (selectedRenderMode == RenderMode.FastGrid)
         <GridColumn TRowData="WeatherForecast" Title="Summary">@context.Summary</GridColumn>
     </Grid>
 }
-else if (selectedRenderMode == RenderMode.PlainTable)
+else if (SelectedRenderMode == RenderMode.PlainTable)
 {
     <p>PlainTable represents a minimal but not optimized implementation of a grid.</p>
 
     <Wasm.Performance.TestApp.Shared.PlainTable.TableComponent Data="@forecasts" Columns="@Columns" />
 }
-else if (selectedRenderMode == RenderMode.ComplexTable)
+else if (SelectedRenderMode == RenderMode.ComplexTable)
 {
     <p>ComplexTable represents a maximal, not optimized implementation of a grid, using a wide range of Blazor features at once.</p>
 
@@ -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<string> Columns { get; set; } = new List<string>
@@ -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;
     }

+ 3 - 16
src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/ComplexTable/TableComponent.razor

@@ -1,4 +1,4 @@
-@using WeatherForecast = Pages.GridRendering.WeatherForecast
+@using WeatherForecast = Pages.GridRendering.WeatherForecast
 
 <table class="table">
     <thead>
@@ -13,7 +13,7 @@
         <CascadingValue Value="@this">
             <RowCollection Data="@Data"
                            Columns="@Columns"
-                           OnClick="@RefreshComponent"></RowCollection>
+                           OnClick="@HandleClickEvent"></RowCollection>
         </CascadingValue>
     </tbody>
 </table>
@@ -26,21 +26,8 @@
     [Parameter]
     public List<string> 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);
-    }
 }

+ 3 - 16
src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/PlainTable/TableComponent.razor

@@ -1,4 +1,4 @@
-@using WeatherForecast = Pages.GridRendering.WeatherForecast
+@using WeatherForecast = Pages.GridRendering.WeatherForecast
 
 <table class="table">
     <thead>
@@ -12,7 +12,7 @@
     <tbody>
         <RowCollection Data="@Data"
                        Columns="@Columns"
-                       OnClick="@RefreshComponent"></RowCollection>
+                       OnClick="@HandleClickEvent"></RowCollection>
     </tbody>
 </table>
 
@@ -24,21 +24,8 @@
     [Parameter]
     public List<string> 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);
-    }
 }