diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs
index 5e0a86d384..d5dc22cde0 100644
--- a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs
+++ b/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs
@@ -66,7 +66,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Tasks
protected override string GenerateFullPathToTool() => DotNetPath;
- protected override string GenerateCommandLineCommands() => ILLinkPath;
+ protected override string GenerateCommandLineCommands()
+ {
+ var args = new StringBuilder();
+ args.Append(Quote(ILLinkPath));
+ return args.ToString();
+ }
private static string Quote(string path)
{
diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs b/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs
index b383d183e4..1984de0a57 100644
--- a/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs
+++ b/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs
@@ -1,6 +1,7 @@
// 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.Linq;
using System.Reflection;
@@ -27,12 +28,23 @@ namespace Microsoft.AspNetCore.Blazor.Build
public override bool Execute()
{
var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name;
- var assemblies = References.Select(c => Path.GetFileName(c.ItemSpec)).ToArray();
+ var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray();
using var fileStream = File.Create(OutputPath);
WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled);
return true;
+
+ static string GetUriPath(ITaskItem item)
+ {
+ var outputPath = item.GetMetadata("RelativeOutputPath");
+ if (string.IsNullOrEmpty(outputPath))
+ {
+ outputPath = Path.GetFileName(item.ItemSpec);
+ }
+
+ return outputPath.Replace('\\', '/');
+ }
}
internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled)
diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
index cdbe7169a0..3c7d126561 100644
--- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
+++ b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets
@@ -74,7 +74,7 @@
-
+
$(IntermediateOutputPath)blazor\
@@ -82,6 +82,8 @@
$(BlazorIntermediateOutputPath)linker.descriptor.xml
+ <_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml
+
$(BlazorIntermediateOutputPath)linker/
@@ -94,8 +96,6 @@
- <_BlazorDependencyInput Include="@(ReferenceCopyLocalPaths->WithMetadataValue('Extension','.dll')->'%(FullPath)')" />
-
<_WebAssemblyBCLFolder Include="
$(DotNetWebAssemblyBCLPath);
$(DotNetWebAssemblyBCLFacadesPath);
@@ -104,6 +104,22 @@
<_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" />
+
+
+
+ <_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" />
+
+
+ <_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" />
+ <_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" />
+
+ <_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" />
+ <_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" />
+
+
@@ -111,6 +127,27 @@
+
+
+
+ <_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" />
+ <_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" />
+
+
+ true
+ $(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)
+ %(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)
+
+
+
+ true
+ $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)
+ %(FileName)%(Extension)
+
+
-
+
+
+
-
- $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)
-
+ <_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" />
+
+
+ <_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
+ <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
+ <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
+
+ <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
+ <_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" />
+
+ <_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
+ <_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" />
+ <_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" />
+
@@ -157,31 +210,30 @@
-
+
+
+
+
+
+
+
+
+
+
-
- <_BlazorDependencyAssembly Include="@(_BlazorDependencyInput)" />
- <_BlazorDependencyAssembly IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" />
- <_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" />
- <_BlazorDependencyAssembly IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" />
-
- <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" />
- <_BlazorAssemblyToLink Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' == 'true'" />
-
- <_BlazorLinkerRoot Include="@(IntermediateAssembly)" />
- <_BlazorLinkerRoot Include="@(_BlazorDependencyAssembly)" Condition="'%(_BlazorDependencyAssembly.IsLinkable)' != 'true'" />
-
-
<_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)
@@ -203,15 +255,6 @@
<_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe
-
- <_TypeGranularityLinkingConfig>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
- $(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)
-
-
-
+
@@ -282,13 +318,12 @@
Inputs="@(BlazorOutputWithTargetPath)"
Outputs="$(BlazorBootJsonIntermediateOutputPath)">
- <_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.dll'))" />
- <_AppReferences Include="@(BlazorOutputWithTargetPath->WithMetadataValue('Extension','.pdb'))" Condition="'$(BlazorEnableDebugging)' == 'true'" />
+ <_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" />
diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs
index dbc8aff929..a1fc282910 100644
--- a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs
+++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs
@@ -70,5 +70,66 @@ namespace Microsoft.AspNetCore.Blazor.Build
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output.
}
+
+ [Fact]
+ public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput()
+ {
+ // Arrange
+ using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
+ project.AddProjectFileContent(
+@"
+
+ $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies
+
+
+
+");
+
+ var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
+
+ Assert.BuildPassed(result);
+
+ var buildOutputDirectory = project.BuildOutputDirectory;
+
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
+
+ var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
+ Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
+ Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
+ }
+
+ [Fact]
+ public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput()
+ {
+ // Arrange
+ using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
+ project.AddProjectFileContent(
+@"
+
+ false
+ $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies
+
+
+
+");
+
+ var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore");
+
+ Assert.BuildPassed(result);
+
+ var buildOutputDirectory = project.BuildOutputDirectory;
+
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll");
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll");
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
+ Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
+
+ var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json");
+ Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
+ Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
+ }
}
}
diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs
index 59dc691c0c..69a27473cc 100644
--- a/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs
+++ b/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs
@@ -112,6 +112,35 @@ namespace Microsoft.AspNetCore.Blazor.Build
Assert.FileExists(result, publishDirectory, "web.config");
}
+ [Fact]
+ public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput()
+ {
+ // Arrange
+ using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" });
+ project.AddProjectFileContent(
+@"
+
+ $(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies
+
+
+
+");
+
+ var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore");
+
+ Assert.BuildPassed(result);
+
+ var publishDirectory = project.PublishOutputDirectory;
+ var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath));
+
+ Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll");
+ Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output.
+
+ var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json");
+ Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\"");
+ Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\"");
+ }
+
[Fact]
public async Task Publish_HostedApp_Works()
{
diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs
new file mode 100644
index 0000000000..944699cdb3
--- /dev/null
+++ b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace classlibrarywithsatelliteassemblies
+{
+ public class Class1
+ {
+ public static void Test()
+ {
+ GC.KeepAlive(typeof(Microsoft.CodeAnalysis.CSharp.CSharpCompilation));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj
new file mode 100644
index 0000000000..7081842748
--- /dev/null
+++ b/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.1
+ 3.0
+
+
+
+
+
+
+
+
diff --git a/src/Components/Blazor/Build/testassets/standalone/Program.cs b/src/Components/Blazor/Build/testassets/standalone/Program.cs
index 16bfae7e43..3e46e63316 100644
--- a/src/Components/Blazor/Build/testassets/standalone/Program.cs
+++ b/src/Components/Blazor/Build/testassets/standalone/Program.cs
@@ -1,10 +1,14 @@
-
+using System;
+
namespace standalone
{
public class Program
{
public static void Main(string[] args)
{
+#if REFERENCE_classlibrarywithsatelliteassemblies
+ GC.KeepAlive(typeof(classlibrarywithsatelliteassemblies.Class1));
+#endif
}
}
}
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/index.js b/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/index.js
deleted file mode 100644
index 4600066f38..0000000000
--- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { HtmlUI } from './lib/minibench/minibench.js';
-import './appStartup.js';
-import './renderList.js';
-import './jsonHandling.js';
-
-new HtmlUI('E2E Performance', '#display');
diff --git a/src/Components/Components.sln b/src/Components/Components.sln
index 91278955cf..c88695cf66 100644
--- a/src/Components/Components.sln
+++ b/src/Components/Components.sln
@@ -21,8 +21,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DevServer", "Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.E2EPerformance", "Blazor\testassets\Microsoft.AspNetCore.Blazor.E2EPerformance\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj", "{08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}"
@@ -238,8 +236,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Validation", "Validation", "{FD9BD646-9D50-42ED-A3E1-90558BA0C6B2}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}"
@@ -250,6 +246,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -344,18 +348,6 @@ Global
{A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x64.Build.0 = Release|Any CPU
{A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x86.ActiveCfg = Release|Any CPU
{A6C8050D-7C18-4585-ADCF-833AC1765847}.Release|x86.Build.0 = Release|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x64.ActiveCfg = Debug|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x64.Build.0 = Debug|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x86.ActiveCfg = Debug|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Debug|x86.Build.0 = Debug|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|Any CPU.Build.0 = Release|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x64.ActiveCfg = Release|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x64.Build.0 = Release|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x86.ActiveCfg = Release|Any CPU
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB}.Release|x86.Build.0 = Release|Any CPU
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -1532,6 +1524,30 @@ Global
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x64.Build.0 = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.ActiveCfg = Release|Any CPU
{F2E27E1C-2E47-42C1-9AC7-36265A381717}.Release|x86.Build.0 = Release|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x64.Build.0 = Debug|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Debug|x86.Build.0 = Debug|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x64.ActiveCfg = Release|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x64.Build.0 = Release|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x86.ActiveCfg = Release|Any CPU
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457}.Release|x86.Build.0 = Release|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x64.Build.0 = Debug|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Debug|x86.Build.0 = Debug|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.ActiveCfg = Release|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.Build.0 = Release|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = Release|Any CPU
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1544,7 +1560,6 @@ Global
{E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
- {08773DD6-6FED-4BF2-BD9F-C19D2CF919BB} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10}
@@ -1641,12 +1656,14 @@ Global
{BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED}
{CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
{F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926}
- {FD9BD646-9D50-42ED-A3E1-90558BA0C6B2} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}
{D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}
{F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699}
+ {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367}
+ {CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
+ {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE}
diff --git a/src/Components/ComponentsNoDeps.slnf b/src/Components/ComponentsNoDeps.slnf
index 61501c5101..7e09eeea25 100644
--- a/src/Components/ComponentsNoDeps.slnf
+++ b/src/Components/ComponentsNoDeps.slnf
@@ -13,14 +13,12 @@
"Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj",
"Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj",
"Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj",
+ "Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
"Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj",
- "Blazor\\Templates\\src\\Microsoft.AspNetCore.Blazor.Templates.csproj",
"Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj",
"Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj",
- "Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj",
"Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
- "Blazor\\testassets\\Microsoft.AspNetCore.Blazor.E2EPerformance\\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj",
"Blazor\\testassets\\MonoSanityClient\\MonoSanityClient.csproj",
"Blazor\\testassets\\MonoSanity\\MonoSanity.csproj",
"Blazor\\testassets\\StandaloneApp\\StandaloneApp.csproj",
@@ -36,6 +34,8 @@
"Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
"Web\\src\\Microsoft.AspNetCore.Components.Web.csproj",
"Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj",
+ "benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
+ "benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
"test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj",
"test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
"test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
diff --git a/src/Components/benchmarkapps/Directory.Build.props b/src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.props
similarity index 100%
rename from src/Components/benchmarkapps/Directory.Build.props
rename to src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.props
diff --git a/src/Components/benchmarkapps/Directory.Build.targets b/src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.targets
similarity index 100%
rename from src/Components/benchmarkapps/Directory.Build.targets
rename to src/Components/benchmarkapps/BlazingPizza.Server/Directory.Build.targets
diff --git a/src/Components/benchmarkapps/NuGet.config b/src/Components/benchmarkapps/BlazingPizza.Server/NuGet.config
similarity index 100%
rename from src/Components/benchmarkapps/NuGet.config
rename to src/Components/benchmarkapps/BlazingPizza.Server/NuGet.config
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs
new file mode 100644
index 0000000000..62016cf630
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs
@@ -0,0 +1,14 @@
+// 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;
+
+namespace Wasm.Performance.Driver
+{
+ internal class BenchmarkMeasurement
+ {
+ public DateTime Timestamp { get; internal set; }
+ public string Name { get; internal set; }
+ public double Value { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMetadata.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMetadata.cs
new file mode 100644
index 0000000000..ab98fef891
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMetadata.cs
@@ -0,0 +1,14 @@
+// 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.
+
+namespace Wasm.Performance.Driver
+{
+ internal class BenchmarkMetadata
+ {
+ public string Source { get; set; }
+ public string Name { get; set; }
+ public string ShortDescription { get; set; }
+ public string LongDescription { get; set; }
+ public string Format { get; set; }
+ }
+}
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkOutput.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkOutput.cs
new file mode 100644
index 0000000000..7a32ce146d
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkOutput.cs
@@ -0,0 +1,14 @@
+// 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.Collections.Generic;
+
+namespace Wasm.Performance.Driver
+{
+ internal class BenchmarkOutput
+ {
+ public List Metadata { get; } = new List();
+
+ public List Measurements { get; } = new List();
+ }
+}
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs
new file mode 100644
index 0000000000..3173341e4b
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs
@@ -0,0 +1,13 @@
+namespace Wasm.Performance.Driver
+{
+ class BenchmarkResult
+ {
+ public string Name { get; set; }
+
+ public bool Success { get; set; }
+
+ public int NumExecutions { get; set; }
+
+ public double Duration { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs
new file mode 100644
index 0000000000..7a4af028df
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs
@@ -0,0 +1,37 @@
+// 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.Collections.Generic;
+using System.Text.Json;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Wasm.Performance.Driver
+{
+ public class BenchmarkDriverStartup
+ {
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddCors(c => c.AddDefaultPolicy(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()));
+ }
+
+ public void Configure(IApplicationBuilder app)
+ {
+ app.UseCors();
+
+ app.Run(async context =>
+ {
+ var result = await JsonSerializer.DeserializeAsync>(context.Request.Body, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ });
+ await context.Response.WriteAsync("OK");
+ Program.SetBenchmarkResult(result);
+ });
+ }
+ }
+}
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs
new file mode 100644
index 0000000000..cfaa9cef0f
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs
@@ -0,0 +1,232 @@
+// 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.IO;
+using System.Linq;
+using System.Runtime.ExceptionServices;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using DevHostServerProgram = Microsoft.AspNetCore.Blazor.DevServer.Server.Program;
+
+namespace Wasm.Performance.Driver
+{
+ public class Program
+ {
+ static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3);
+ static TaskCompletionSource> benchmarkResult = new TaskCompletionSource>();
+
+ public static async Task Main(string[] args)
+ {
+ var seleniumPort = 4444;
+ if (args.Length > 0)
+ {
+ if (!int.TryParse(args[0], out seleniumPort))
+ {
+ Console.Error.WriteLine("Usage Driver ");
+ return 1;
+ }
+ }
+
+ // This write is required for the benchmarking infrastructure.
+ Console.WriteLine("Application started.");
+
+ var cancellationToken = new CancellationTokenSource(Timeout);
+ cancellationToken.Token.Register(() => benchmarkResult.TrySetException(new TimeoutException($"Timed out after {Timeout}")));
+
+ using var browser = await Selenium.CreateBrowser(seleniumPort, cancellationToken.Token);
+ using var testApp = StartTestApp();
+ using var benchmarkReceiver = StartBenchmarkResultReceiver();
+
+ var testAppUrl = GetListeningUrl(testApp);
+ var receiverUrl = GetListeningUrl(benchmarkReceiver);
+
+ Console.WriteLine($"Test app listening at {testAppUrl}.");
+
+ var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated";
+ browser.Url = launchUrl;
+ browser.Navigate();
+
+ var results = await benchmarkResult.Task;
+ FormatAsBenchmarksOutput(results);
+
+ Console.WriteLine("Done executing benchmark");
+ return 0;
+ }
+
+ internal static void SetBenchmarkResult(List result)
+ {
+ benchmarkResult.TrySetResult(result);
+ }
+
+ private static void FormatAsBenchmarksOutput(List results)
+ {
+ // Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67
+ var output = new BenchmarkOutput();
+ foreach (var result in results)
+ {
+ output.Metadata.Add(new BenchmarkMetadata
+ {
+ Source = "BlazorWasm",
+ Name = result.Name,
+ ShortDescription = $"{result.Name} Duration",
+ LongDescription = $"{result.Name} Duration",
+ Format = "n2"
+ });
+
+ output.Measurements.Add(new BenchmarkMeasurement
+ {
+ Timestamp = DateTime.UtcNow,
+ Name = result.Name,
+ Value = result.Duration,
+ });
+ }
+
+ // Statistics about publish sizes
+ output.Metadata.Add(new BenchmarkMetadata
+ {
+ Source = "BlazorWasm",
+ Name = "Publish size",
+ ShortDescription = "Publish size (KB)",
+ LongDescription = "Publish size (KB)",
+ Format = "n2",
+ });
+
+ var testAssembly = typeof(TestApp.Startup).Assembly;
+ var testAssemblyLocation = new FileInfo(testAssembly.Location);
+ var testApp = new DirectoryInfo(Path.Combine(
+ testAssemblyLocation.Directory.FullName,
+ testAssembly.GetName().Name));
+
+ output.Measurements.Add(new BenchmarkMeasurement
+ {
+ Timestamp = DateTime.UtcNow,
+ Name = "Publish size",
+ Value = GetDirectorySize(testApp) / 1024,
+ });
+
+ output.Metadata.Add(new BenchmarkMetadata
+ {
+ Source = "BlazorWasm",
+ Name = "Publish size (compressed)",
+ ShortDescription = "Publish size compressed app (KB)",
+ LongDescription = "Publish size - compressed app (KB)",
+ Format = "n2",
+ });
+
+ var gzip = new FileInfo(Path.Combine(
+ testAssemblyLocation.Directory.FullName,
+ $"{testAssembly.GetName().Name}.gzip"));
+
+ output.Measurements.Add(new BenchmarkMeasurement
+ {
+ Timestamp = DateTime.UtcNow,
+ Name = "Publish size (compressed)",
+ Value = (gzip.Exists ? gzip.Length : 0) / 1024,
+ });
+
+ Console.WriteLine("#StartJobStatistics");
+ Console.WriteLine(JsonSerializer.Serialize(output));
+ Console.WriteLine("#EndJobStatistics");
+ }
+
+ static IHost StartTestApp()
+ {
+ var args = new[]
+ {
+ "--urls", "http://127.0.0.1:0",
+ "--applicationpath", typeof(TestApp.Startup).Assembly.Location,
+ };
+
+ var host = DevHostServerProgram.BuildWebHost(args);
+ RunInBackgroundThread(host.Start);
+ return host;
+ }
+
+ static IHost StartBenchmarkResultReceiver()
+ {
+ var args = new[]
+ {
+ "--urls", "http://127.0.0.1:0",
+ };
+
+ var host = Host.CreateDefaultBuilder(args)
+ .ConfigureWebHostDefaults(builder => builder.UseStartup())
+ .Build();
+
+ RunInBackgroundThread(host.Start);
+ return host;
+ }
+
+ static void RunInBackgroundThread(Action action)
+ {
+ var isDone = new ManualResetEvent(false);
+
+ ExceptionDispatchInfo edi = null;
+ Task.Run(() =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception ex)
+ {
+ edi = ExceptionDispatchInfo.Capture(ex);
+ }
+
+ isDone.Set();
+ });
+
+ if (!isDone.WaitOne(Timeout))
+ {
+ throw new TimeoutException("Timed out waiting for: " + action);
+ }
+
+ if (edi != null)
+ {
+ throw edi.SourceException;
+ }
+ }
+
+ static string GetListeningUrl(IHost testApp)
+ {
+ return testApp.Services.GetRequiredService()
+ .Features
+ .Get()
+ .Addresses
+ .First();
+ }
+
+ static long GetDirectorySize(DirectoryInfo directory)
+ {
+ // This can happen if you run the app without publishing it.
+ if (!directory.Exists)
+ {
+ return 0;
+ }
+
+ long size = 0;
+ foreach (var item in directory.EnumerateFileSystemInfos())
+ {
+ if (item is FileInfo fileInfo)
+ {
+ size += fileInfo.Length;
+ }
+ else if (item is DirectoryInfo directoryInfo)
+ {
+ size += GetDirectorySize(directoryInfo);
+ }
+ }
+
+ return size;
+ }
+ }
+}
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs
new file mode 100644
index 0000000000..1c30e69e20
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs
@@ -0,0 +1,121 @@
+// 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.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using OpenQA.Selenium;
+using OpenQA.Selenium.Chrome;
+using OpenQA.Selenium.Remote;
+
+namespace Wasm.Performance.Driver
+{
+ class Selenium
+ {
+ static bool RunHeadlessBrowser = true;
+ static bool PoolForBrowserLogs = true;
+
+ private static async ValueTask WaitForServerAsync(int port, CancellationToken cancellationToken)
+ {
+ var uri = new UriBuilder("http", "localhost", port, "/wd/hub/").Uri;
+ var httpClient = new HttpClient
+ {
+ BaseAddress = uri,
+ Timeout = TimeSpan.FromSeconds(1),
+ };
+
+ Console.WriteLine($"Attempting to connect to Selenium Server running at {uri}");
+
+ const int MaxRetries = 30;
+ var retries = 0;
+
+ while (retries < MaxRetries)
+ {
+ retries++;
+ try
+ {
+ var response = (await httpClient.GetAsync("status", cancellationToken)).EnsureSuccessStatusCode();
+ Console.WriteLine("Connected to Selenium");
+ return uri;
+ }
+ catch
+ {
+ if (retries == 1)
+ {
+ Console.WriteLine("Could not connect to selenium-server. Has it been started as yet?");
+ }
+ }
+
+ await Task.Delay(1000);
+ }
+
+ throw new Exception($"Unable to connect to selenium-server at {uri}");
+ }
+
+ public static async Task CreateBrowser(int port, CancellationToken cancellationToken)
+ {
+ var uri = await WaitForServerAsync(port, cancellationToken);
+
+ var options = new ChromeOptions();
+
+ if (RunHeadlessBrowser)
+ {
+ options.AddArgument("--headless");
+ }
+
+ options.SetLoggingPreference(LogType.Browser, LogLevel.All);
+
+ var attempt = 0;
+ const int MaxAttempts = 3;
+ do
+ {
+ try
+ {
+ // The driver opens the browser window and tries to connect to it on the constructor.
+ // Under heavy load, this can cause issues
+ // To prevent this we let the client attempt several times to connect to the server, increasing
+ // the max allowed timeout for a command on each attempt linearly.
+ var driver = new RemoteWebDriver(
+ uri,
+ options.ToCapabilities(),
+ TimeSpan.FromSeconds(60).Add(TimeSpan.FromSeconds(attempt * 60)));
+
+ driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1);
+
+ if (PoolForBrowserLogs)
+ {
+ // Run in background.
+ var logs = new RemoteLogs(driver);
+ _ = Task.Run(async () =>
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(3));
+
+ var consoleLogs = logs.GetLog(LogType.Browser);
+ foreach (var entry in consoleLogs)
+ {
+ Console.WriteLine($"[Browser Log]: {entry.Timestamp}: {entry.Message}");
+ }
+ }
+ });
+ }
+
+ return driver;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error initializing RemoteWebDriver: {ex.Message}");
+ }
+
+ attempt++;
+
+ } while (attempt < MaxAttempts);
+
+ throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
+ }
+ }
+}
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj
new file mode 100644
index 0000000000..cf35be4e00
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+ netcoreapp3.1
+
+ true
+ exe
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/appsettings.json b/src/Components/benchmarkapps/Wasm.Performance/Driver/appsettings.json
new file mode 100644
index 0000000000..bed61b254f
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "IncludeScopes": false,
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/benchmarkapps/Wasm.Performance/README.md b/src/Components/benchmarkapps/Wasm.Performance/README.md
new file mode 100644
index 0000000000..9522ecc502
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/README.md
@@ -0,0 +1,20 @@
+## Blazor WASM benchmarks
+
+These projects assist in Benchmarking Components.
+See https://github.com/aspnet/Benchmarks#benchmarks for usage guidance on using the Benchmarking tool with your application
+
+### Running the benchmarks
+
+The TestApp is a regular BlazorWASM project and can be run using `dotnet run`. The Driver is an app that connects against an existing Selenium server, and speaks the Benchmark protocol. You generally do not need to run the Driver locally, but if you were to do so, you can either start a selenium-server instance and run using `dotnet run []` or run it inside a Linux-based docker container.
+
+Here are the commands you would need to run it locally inside docker:
+
+1. `dotnet publish -c Release -r linux-x64 Driver/Wasm.Performance.Driver.csproj`
+2. `docker build -t blazor-local -f ./local.dockerfile . `
+3. `docker run -it blazor-local`
+
+To run the benchmark app in the Benchmark server, run
+
+```
+dotnet run -- --config aspnetcore/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json application.endpoints --scenario blazorwasmbenchmark
+```
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/App.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/App.razor
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/App.razor
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/BenchmarkEvent.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs
similarity index 89%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/BenchmarkEvent.cs
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs
index bdf98fd388..81cd361dce 100644
--- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/BenchmarkEvent.cs
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/BenchmarkEvent.cs
@@ -3,7 +3,7 @@
using Microsoft.JSInterop;
-namespace Microsoft.AspNetCore.Blazor.E2EPerformance
+namespace Wasm.Performance.TestApp
{
public static class BenchmarkEvent
{
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Index.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Index.razor
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Json.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/Json.razor
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/RenderList.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/RenderList.razor
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/RenderList.razor
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/RenderList.razor
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/_Imports.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/_Imports.razor
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Pages/_Imports.razor
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/_Imports.razor
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs
similarity index 91%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Program.cs
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs
index f498eb0222..403bc37c9c 100644
--- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Program.cs
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs
@@ -3,7 +3,7 @@
using Microsoft.AspNetCore.Blazor.Hosting;
-namespace Microsoft.AspNetCore.Blazor.E2EPerformance
+namespace Wasm.Performance.TestApp
{
public class Program
{
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Shared/MainLayout.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Shared/MainLayout.razor
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Startup.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs
similarity index 90%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Startup.cs
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs
index 7422cd806c..c79b0efb8c 100644
--- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Startup.cs
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Startup.cs
@@ -4,7 +4,7 @@
using Microsoft.AspNetCore.Components.Builder;
using Microsoft.Extensions.DependencyInjection;
-namespace Microsoft.AspNetCore.Blazor.E2EPerformance
+namespace Wasm.Performance.TestApp
{
public class Startup
{
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Microsoft.AspNetCore.Blazor.E2EPerformance.csproj b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj
similarity index 88%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Microsoft.AspNetCore.Blazor.E2EPerformance.csproj
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj
index 140762810f..3fb5a922a3 100644
--- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/Microsoft.AspNetCore.Blazor.E2EPerformance.csproj
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj
@@ -1,4 +1,4 @@
-
+
netstandard2.1
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/_Imports.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor
similarity index 56%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/_Imports.razor
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor
index dc263c9383..fef56339a9 100644
--- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/_Imports.razor
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor
@@ -2,5 +2,5 @@
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
-@using Microsoft.AspNetCore.Blazor.E2EPerformance
-@using Microsoft.AspNetCore.Blazor.E2EPerformance.Shared
+@using Wasm.Performance.TestApp
+@using Wasm.Performance.TestApp.Shared
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/appStartup.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/appStartup.js
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/appStartup.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/appStartup.js
diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js
new file mode 100644
index 0000000000..c1690cfac8
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js
@@ -0,0 +1,39 @@
+import { groups, BenchmarkEvent, onBenchmarkEvent } from './lib/minibench/minibench.js';
+import { HtmlUI } from './lib/minibench/minibench.ui.js';
+import './appStartup.js';
+import './renderList.js';
+import './jsonHandling.js';
+
+new HtmlUI('E2E Performance', '#display');
+
+if (location.href.indexOf('#automated') !== -1) {
+ const query = new URLSearchParams(window.location.search);
+ const group = query.get('group');
+ const resultsUrl = query.get('resultsUrl');
+
+ groups.filter(g => !group || g.name === group).forEach(g => g.runAll());
+
+ const benchmarksResults = [];
+ onBenchmarkEvent(async (status, args) => {
+ switch (status) {
+ case BenchmarkEvent.runStarted:
+ benchmarksResults.length = 0;
+ break;
+ case BenchmarkEvent.benchmarkCompleted:
+ case BenchmarkEvent.benchmarkError:
+ console.log(`Completed benchmark ${args.name}`);
+ benchmarksResults.push(args);
+ break;
+ case BenchmarkEvent.runCompleted:
+ if (resultsUrl) {
+ await fetch(resultsUrl, {
+ method: 'post',
+ body: JSON.stringify(benchmarksResults)
+ });
+ }
+ break;
+ default:
+ throw new Error(`Unknown status: ${status}`);
+ }
+ })
+}
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandling.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandling.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandlingData.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/jsonHandlingData.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/bootstrap.min.css b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/bootstrap.min.css
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/bootstrap.min.css
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/bootstrap.min.css
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/README.md b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/README.md
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/README.md
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/README.md
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/minibench.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.js
similarity index 58%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/minibench.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.js
index 8214419982..241721ceeb 100644
--- a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/minibench.js
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.js
@@ -66,7 +66,7 @@ window.addEventListener('message', evt => {
To work around browsers' current nonsupport for high-resolution timers
(since Spectre etc.), the approach used here is to group executions into
blocks of roughly fixed duration.
-
+
- In each block, we execute the test code as many times as we can until
the end of the block duration, without even yielding the thread if
it's a synchronous call. We count how many executions completed. It
@@ -82,7 +82,7 @@ window.addEventListener('message', evt => {
during which there was no unrelated GC cycle or other background contention.
- We keep running blocks until some larger timeout occurs *and* we've done
at least some minimum number of executions.
-
+
Note that this approach does *not* allow for per-execution setup/teardown
logic whose timing is separated from the code under test. Because of the
low timer precision, there would be no way to separate the setup duration
@@ -174,10 +174,23 @@ class Benchmark extends EventEmitter {
}
run(runOptions) {
+ if (reportBenchmarkEvent) {
+ const areAllIdle = groups.reduce(
+ (prev, next) => prev && next.status === BenchmarkStatus.idle,
+ true
+ );
+
+ if (areAllIdle) {
+ // This is the first test being run from the idle state
+ reportBenchmarkEvent(BenchmarkEvent.runStarted);
+ }
+ }
+
this._currentRunWasAborted = false;
if (this._state.status === BenchmarkStatus.idle) {
this._updateState({ status: BenchmarkStatus.queued });
this.workQueueCancelHandle = addToWorkQueue(async () => {
+
try {
if (!(runOptions && runOptions.skipGroupSetup)) {
await this._group.runSetup();
@@ -192,10 +205,13 @@ class Benchmark extends EventEmitter {
await this._group.runTeardown();
}
+ reportBenchmarkEvent(BenchmarkEvent.benchmarkCompleted, { 'name': this.name, success: true, numExecutions: this._state.numExecutions, duration: this._state.estimatedExecutionDurationMs });
+
this._updateState({ status: BenchmarkStatus.idle });
} catch (ex) {
this._updateState({ status: BenchmarkStatus.error });
console.error(ex);
+ reportBenchmarkEvent(BenchmarkEvent.benchmarkError, { 'name': this.name, success: false });
}
});
}
@@ -237,6 +253,13 @@ const BenchmarkStatus = {
error: 3,
};
+const BenchmarkEvent = {
+ runStarted: 0,
+ benchmarkCompleted : 1,
+ benchmarkError: 2,
+ runCompleted: 3,
+}
+
class Group extends EventEmitter {
constructor(name) {
super();
@@ -279,6 +302,7 @@ class Group extends EventEmitter {
}
const groups = [];
+let reportBenchmarkEvent = () => {};
function group(name, configure) {
groups.push(new Group(name));
@@ -298,184 +322,21 @@ function teardown(fn) {
groups[groups.length - 1].teardown = fn;
}
-class BenchmarkDisplay {
- constructor(htmlUi, benchmark) {
- this.benchmark = benchmark;
- this.elem = document.createElement('tr');
-
- const headerCol = this.elem.appendChild(document.createElement('th'));
- headerCol.className = 'pl-4';
- headerCol.textContent = benchmark.name;
- headerCol.setAttribute('scope', 'row');
+function onBenchmarkEvent(fn) {
+ reportBenchmarkEvent = fn;
- const progressCol = this.elem.appendChild(document.createElement('td'));
- this.numExecutionsText = progressCol.appendChild(document.createTextNode(''));
+ groups.forEach(group$$1 => {
+ group$$1.on('changed', () => {
+ const areAllIdle = groups.reduce(
+ (prev, next) => prev && next.status === BenchmarkStatus.idle,
+ true
+ );
- const timingCol = this.elem.appendChild(document.createElement('td'));
- this.executionDurationText = timingCol.appendChild(document.createElement('span'));
-
- const runCol = this.elem.appendChild(document.createElement('td'));
- runCol.className = 'pr-4';
- runCol.setAttribute('align', 'right');
- this.runButton = document.createElement('a');
- this.runButton.className = 'run-button';
- runCol.appendChild(this.runButton);
- this.runButton.textContent = 'Run';
- this.runButton.onclick = evt => {
- evt.preventDefault();
- this.benchmark.run(htmlUi.globalRunOptions);
- };
-
- benchmark.on('changed', state => this.updateDisplay(state));
- this.updateDisplay(this.benchmark.state);
- }
-
- updateDisplay(state) {
- const benchmark = this.benchmark;
- this.elem.className = rowClass(state.status);
- this.runButton.textContent = runButtonText(state.status);
- this.numExecutionsText.textContent = state.numExecutions
- ? `Executions: ${state.numExecutions}` : '';
- this.executionDurationText.innerHTML = state.estimatedExecutionDurationMs
- ? `Duration: ${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms` : '';
- if (state.status === BenchmarkStatus.idle) {
- this.runButton.setAttribute('href', '');
- } else {
- this.runButton.removeAttribute('href');
- if (state.status === BenchmarkStatus.error) {
- this.numExecutionsText.textContent = 'Error - see console';
+ if (areAllIdle) {
+ fn(BenchmarkEvent.runCompleted);
}
- }
- }
-}
-
-function runButtonText(status) {
- switch (status) {
- case BenchmarkStatus.idle:
- case BenchmarkStatus.error:
- return 'Run';
- case BenchmarkStatus.queued:
- return 'Waiting...';
- case BenchmarkStatus.running:
- return 'Running...';
- default:
- throw new Error(`Unknown status: ${status}`);
- }
-}
-
-function rowClass(status) {
- switch (status) {
- case BenchmarkStatus.idle:
- return 'benchmark-idle';
- case BenchmarkStatus.queued:
- return 'benchmark-waiting';
- case BenchmarkStatus.running:
- return 'benchmark-running';
- case BenchmarkStatus.error:
- return 'benchmark-error';
- default:
- throw new Error(`Unknown status: ${status}`);
- }
-}
-
-class GroupDisplay {
- constructor(htmlUi, group) {
- this.group = group;
-
- this.elem = document.createElement('div');
- this.elem.className = 'my-3 py-2 bg-white rounded shadow-sm';
-
- const headerContainer = this.elem.appendChild(document.createElement('div'));
- headerContainer.className = 'd-flex align-items-baseline px-4';
- const header = headerContainer.appendChild(document.createElement('h5'));
- header.className = 'py-2';
- header.textContent = group.name;
-
- this.runButton = document.createElement('a');
- this.runButton.className = 'ml-auto run-button';
- this.runButton.setAttribute('href', '');
- headerContainer.appendChild(this.runButton);
- this.runButton.textContent = 'Run all';
- this.runButton.onclick = evt => {
- evt.preventDefault();
- group.runAll(htmlUi.globalRunOptions);
- };
-
- const table = this.elem.appendChild(document.createElement('table'));
- table.className = 'table mb-0 benchmarks';
- const tbody = table.appendChild(document.createElement('tbody'));
-
- group.benchmarks.forEach(benchmark => {
- const benchmarkDisplay = new BenchmarkDisplay(htmlUi, benchmark);
- tbody.appendChild(benchmarkDisplay.elem);
});
-
- group.on('changed', () => this.updateDisplay());
- this.updateDisplay();
- }
-
- updateDisplay() {
- const canRun = this.group.status === BenchmarkStatus.idle;
- this.runButton.style.display = canRun ? 'block' : 'none';
- }
-}
-
-class HtmlUI {
- constructor(title, selector) {
- this.containerElement = document.querySelector(selector);
-
- const headerDiv = this.containerElement.appendChild(document.createElement('div'));
- headerDiv.className = 'd-flex align-items-center';
-
- const header = headerDiv.appendChild(document.createElement('h2'));
- header.className = 'mx-3 flex-grow-1';
- header.textContent = title;
-
- const verifyCheckboxLabel = document.createElement('label');
- verifyCheckboxLabel.className = 'ml-auto mr-5';
- headerDiv.appendChild(verifyCheckboxLabel);
- this.verifyCheckbox = verifyCheckboxLabel.appendChild(document.createElement('input'));
- this.verifyCheckbox.type = 'checkbox';
- this.verifyCheckbox.className = 'mr-2';
- verifyCheckboxLabel.appendChild(document.createTextNode('Verify only'));
-
- this.runButton = document.createElement('button');
- this.runButton.className = 'btn btn-success ml-auto px-4 run-button';
- headerDiv.appendChild(this.runButton);
- this.runButton.textContent = 'Run all';
- this.runButton.onclick = () => {
- groups.forEach(g => g.runAll(this.globalRunOptions));
- };
-
- this.stopButton = document.createElement('button');
- this.stopButton.className = 'btn btn-danger ml-auto px-4 stop-button';
- headerDiv.appendChild(this.stopButton);
- this.stopButton.textContent = 'Stop';
- this.stopButton.onclick = () => {
- groups.forEach(g => g.stopAll());
- };
-
- groups.forEach(group$$1 => {
- const groupDisplay = new GroupDisplay(this, group$$1);
- this.containerElement.appendChild(groupDisplay.elem);
- group$$1.on('changed', () => this.updateDisplay());
- });
-
- this.updateDisplay();
- }
-
- updateDisplay() {
- const areAllIdle = groups.reduce(
- (prev, next) => prev && next.status === BenchmarkStatus.idle,
- true
- );
- this.runButton.style.display = areAllIdle ? 'block' : 'none';
- this.stopButton.style.display = areAllIdle ? 'none' : 'block';
- }
-
- get globalRunOptions() {
- return { verifyOnly: this.verifyCheckbox.checked };
- }
+ });
}
/**
@@ -483,4 +344,4 @@ class HtmlUI {
* https://github.com/SteveSanderson/minibench
*/
-export { group, benchmark, setup, teardown, HtmlUI };
+export { groups, group, benchmark, setup, teardown, onBenchmarkEvent, BenchmarkEvent, BenchmarkStatus };
diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js
new file mode 100644
index 0000000000..4384b7660b
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js
@@ -0,0 +1,191 @@
+/** minibench - https://github.com/SteveSanderson/minibench */
+
+import { groups, BenchmarkStatus } from './minibench.js';
+
+class BenchmarkDisplay {
+ constructor(htmlUi, benchmark) {
+ this.benchmark = benchmark;
+ this.elem = document.createElement('tr');
+
+ const headerCol = this.elem.appendChild(document.createElement('th'));
+ headerCol.className = 'pl-4';
+ headerCol.textContent = benchmark.name;
+ headerCol.setAttribute('scope', 'row');
+
+ const progressCol = this.elem.appendChild(document.createElement('td'));
+ this.numExecutionsText = progressCol.appendChild(document.createTextNode(''));
+
+ const timingCol = this.elem.appendChild(document.createElement('td'));
+ this.executionDurationText = timingCol.appendChild(document.createElement('span'));
+
+ const runCol = this.elem.appendChild(document.createElement('td'));
+ runCol.className = 'pr-4';
+ runCol.setAttribute('align', 'right');
+ this.runButton = document.createElement('a');
+ this.runButton.className = 'run-button';
+ runCol.appendChild(this.runButton);
+ this.runButton.textContent = 'Run';
+ this.runButton.onclick = evt => {
+ evt.preventDefault();
+ this.benchmark.run(htmlUi.globalRunOptions);
+ };
+
+ benchmark.on('changed', state => this.updateDisplay(state));
+ this.updateDisplay(this.benchmark.state);
+ }
+
+ updateDisplay(state) {
+ const benchmark = this.benchmark;
+ this.elem.className = rowClass(state.status);
+ this.runButton.textContent = runButtonText(state.status);
+ this.numExecutionsText.textContent = state.numExecutions
+ ? `Executions: ${state.numExecutions}` : '';
+ this.executionDurationText.innerHTML = state.estimatedExecutionDurationMs
+ ? `Duration: ${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms` : '';
+ if (state.status === BenchmarkStatus.idle) {
+ this.runButton.setAttribute('href', '');
+ } else {
+ this.runButton.removeAttribute('href');
+ if (state.status === BenchmarkStatus.error) {
+ this.numExecutionsText.textContent = 'Error - see console';
+ }
+ }
+ }
+}
+
+function runButtonText(status) {
+ switch (status) {
+ case BenchmarkStatus.idle:
+ case BenchmarkStatus.error:
+ return 'Run';
+ case BenchmarkStatus.queued:
+ return 'Waiting...';
+ case BenchmarkStatus.running:
+ return 'Running...';
+ default:
+ throw new Error(`Unknown status: ${status}`);
+ }
+}
+
+function rowClass(status) {
+ switch (status) {
+ case BenchmarkStatus.idle:
+ return 'benchmark-idle';
+ case BenchmarkStatus.queued:
+ return 'benchmark-waiting';
+ case BenchmarkStatus.running:
+ return 'benchmark-running';
+ case BenchmarkStatus.error:
+ return 'benchmark-error';
+ default:
+ throw new Error(`Unknown status: ${status}`);
+ }
+}
+
+class GroupDisplay {
+ constructor(htmlUi, group) {
+ this.group = group;
+
+ this.elem = document.createElement('div');
+ this.elem.className = 'my-3 py-2 bg-white rounded shadow-sm';
+
+ const headerContainer = this.elem.appendChild(document.createElement('div'));
+ headerContainer.className = 'd-flex align-items-baseline px-4';
+ const header = headerContainer.appendChild(document.createElement('h5'));
+ header.className = 'py-2';
+ header.textContent = group.name;
+
+ this.runButton = document.createElement('a');
+ this.runButton.className = 'ml-auto run-button';
+ this.runButton.setAttribute('href', '');
+ headerContainer.appendChild(this.runButton);
+ this.runButton.textContent = 'Run all';
+ this.runButton.onclick = evt => {
+ evt.preventDefault();
+ group.runAll(htmlUi.globalRunOptions);
+ };
+
+ const table = this.elem.appendChild(document.createElement('table'));
+ table.className = 'table mb-0 benchmarks';
+ const tbody = table.appendChild(document.createElement('tbody'));
+
+ group.benchmarks.forEach(benchmark => {
+ const benchmarkDisplay = new BenchmarkDisplay(htmlUi, benchmark);
+ tbody.appendChild(benchmarkDisplay.elem);
+ });
+
+ group.on('changed', () => this.updateDisplay());
+ this.updateDisplay();
+ }
+
+ updateDisplay() {
+ const canRun = this.group.status === BenchmarkStatus.idle;
+ this.runButton.style.display = canRun ? 'block' : 'none';
+ }
+}
+
+class HtmlUI {
+ constructor(title, selector) {
+ this.containerElement = document.querySelector(selector);
+
+ const headerDiv = this.containerElement.appendChild(document.createElement('div'));
+ headerDiv.className = 'd-flex align-items-center';
+
+ const header = headerDiv.appendChild(document.createElement('h2'));
+ header.className = 'mx-3 flex-grow-1';
+ header.textContent = title;
+
+ const verifyCheckboxLabel = document.createElement('label');
+ verifyCheckboxLabel.className = 'ml-auto mr-5';
+ headerDiv.appendChild(verifyCheckboxLabel);
+ this.verifyCheckbox = verifyCheckboxLabel.appendChild(document.createElement('input'));
+ this.verifyCheckbox.type = 'checkbox';
+ this.verifyCheckbox.className = 'mr-2';
+ verifyCheckboxLabel.appendChild(document.createTextNode('Verify only'));
+
+ this.runButton = document.createElement('button');
+ this.runButton.className = 'btn btn-success ml-auto px-4 run-button';
+ headerDiv.appendChild(this.runButton);
+ this.runButton.textContent = 'Run all';
+ this.runButton.setAttribute('id', 'runAll');
+ this.runButton.onclick = () => {
+ groups.forEach(g => g.runAll(this.globalRunOptions));
+ };
+
+ this.stopButton = document.createElement('button');
+ this.stopButton.className = 'btn btn-danger ml-auto px-4 stop-button';
+ headerDiv.appendChild(this.stopButton);
+ this.stopButton.textContent = 'Stop';
+ this.stopButton.onclick = () => {
+ groups.forEach(g => g.stopAll());
+ };
+
+ groups.forEach(group$$1 => {
+ const groupDisplay = new GroupDisplay(this, group$$1);
+ this.containerElement.appendChild(groupDisplay.elem);
+ group$$1.on('changed', () => this.updateDisplay());
+ });
+
+ this.updateDisplay();
+ }
+
+ updateDisplay() {
+ const areAllIdle = groups.reduce(
+ (prev, next) => prev && next.status === BenchmarkStatus.idle,
+ true
+ );
+ this.runButton.style.display = areAllIdle ? 'block' : 'none';
+ this.stopButton.style.display = areAllIdle ? 'none' : 'block';;
+ }
+
+ get globalRunOptions() {
+ return { verifyOnly: this.verifyCheckbox.checked };
+ }
+}
+
+/**
+ * minibench
+ * https://github.com/SteveSanderson/minibench
+ */
+
+export { HtmlUI };
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/style.css b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/style.css
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/lib/minibench/style.css
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/style.css
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/renderList.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/renderList.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BenchmarkEvents.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BenchmarkEvents.js
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BenchmarkEvents.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BenchmarkEvents.js
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BlazorApp.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/BlazorApp.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/DOM.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/DOM.js
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/benchmarks/util/DOM.js
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/DOM.js
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/blazor-frame.html b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/blazor-frame.html
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/blazor-frame.html
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/blazor-frame.html
diff --git a/src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/index.html b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/index.html
similarity index 100%
rename from src/Components/Blazor/testassets/Microsoft.AspNetCore.Blazor.E2EPerformance/wwwroot/index.html
rename to src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/index.html
diff --git a/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json b/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json
new file mode 100644
index 0000000000..81607364dc
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://raw.githubusercontent.com/aspnet/Benchmarks/master/src/BenchmarksDriver2/benchmarks.schema.json",
+ "scenarios": {
+ "blazorwasmbenchmark": {
+ "application": {
+ "job": "blazorwasmbenchmark"
+ }
+ }
+ },
+ "jobs": {
+ "blazorwasmbenchmark": {
+ "source": {
+ "repository": "https://github.com/dotnet/AspNetCore.git",
+ "branchOrCommit": "blazor-wasm",
+ "dockerfile": "src/Components/benchmarkapps/Wasm.Performance/dockerfile"
+ },
+ "waitForExit": true,
+ "readyStateText": "Application started."
+ }
+ }
+}
diff --git a/src/Components/benchmarkapps/Wasm.Performance/dockerfile b/src/Components/benchmarkapps/Wasm.Performance/dockerfile
new file mode 100644
index 0000000000..69f27a9212
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/dockerfile
@@ -0,0 +1,32 @@
+FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
+
+ARG DEBIAN_FRONTEND=noninteractive
+
+# Setup for nodejs
+RUN curl -sL https://deb.nodesource.com/setup_13.x | bash -
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ libunwind-dev \
+ nodejs \
+ git
+
+ARG gitBranch=blazor-wasm
+
+WORKDIR /src
+ADD https://api.github.com/repos/dotnet/aspnetcore/git/ref/heads/${gitBranch} /aspnetcore.commit
+
+RUN git init \
+ && git fetch https://github.com/aspnet/aspnetcore ${gitBranch} \
+ && git reset --hard FETCH_HEAD \
+ && git submodule update --init
+
+RUN dotnet publish -c Release -r linux-x64 -o /app ./src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj
+RUN chmod +x /app/Wasm.Performance.Driver
+
+WORKDIR /app
+FROM selenium/standalone-chrome:3.141.59-mercury as final
+COPY --from=build ./app ./
+COPY ./exec.sh ./
+
+ENTRYPOINT [ "bash", "./exec.sh" ]
diff --git a/src/Components/benchmarkapps/Wasm.Performance/exec.sh b/src/Components/benchmarkapps/Wasm.Performance/exec.sh
new file mode 100644
index 0000000000..bae38ae1e1
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/exec.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+/opt/bin/start-selenium-standalone.sh&
+./Wasm.Performance.Driver
+
diff --git a/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile
new file mode 100644
index 0000000000..188bc5dc5a
--- /dev/null
+++ b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile
@@ -0,0 +1,7 @@
+FROM selenium/standalone-chrome:3.141.59-mercury as final
+
+WORKDIR /app
+COPY ./Driver/bin/Release/netcoreapp3.1/linux-x64/publish ./
+COPY ./exec.sh ./
+
+ENTRYPOINT [ "bash", "./exec.sh" ]
diff --git a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj
index 0ba4c8311a..8856a1395a 100644
--- a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj
+++ b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj
@@ -35,7 +35,7 @@
-
+
diff --git a/src/Components/test/E2ETest/Tests/PerformanceTest.cs b/src/Components/test/E2ETest/Tests/PerformanceTest.cs
index 652226bf26..f7187a4557 100644
--- a/src/Components/test/E2ETest/Tests/PerformanceTest.cs
+++ b/src/Components/test/E2ETest/Tests/PerformanceTest.cs
@@ -13,11 +13,11 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
public class PerformanceTest
- : ServerTestBase>
+ : ServerTestBase>
{
public PerformanceTest(
BrowserFixture browserFixture,
- DevHostServerFixture serverFixture,
+ DevHostServerFixture serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
@@ -52,10 +52,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
() => runAllButton.Displayed || Browser.FindElements(By.CssSelector(".benchmark-error")).Any(),
TimeSpan.FromSeconds(60));
- var finishedBenchmarks = Browser.FindElements(By.CssSelector(".benchmark-idle"));
- var failedBenchmarks = Browser.FindElements(By.CssSelector(".benchmark-error"));
- Assert.NotEmpty(finishedBenchmarks);
- Assert.Empty(failedBenchmarks);
+ Browser.DoesNotExist(By.CssSelector(".benchmark-error")); // no failures
+ Browser.Exists(By.CssSelector(".benchmark-idle")); // everything's done
}
}
}