Add a benchmarking app for Blazor Wasm (#18015)

* Add a benchmarking app for Blazor Wasm
This commit is contained in:
Pranav K 2020-01-14 11:10:52 -08:00 committed by GitHub
commit d4a348e17b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 878 additions and 217 deletions

View File

@ -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');

View File

@ -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}

View File

@ -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",

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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<BenchmarkMetadata> Metadata { get; } = new List<BenchmarkMetadata>();
public List<BenchmarkMeasurement> Measurements { get; } = new List<BenchmarkMeasurement>();
}
}

View File

@ -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; }
}
}

View File

@ -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<List<BenchmarkResult>>(context.Request.Body, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
await context.Response.WriteAsync("OK");
Program.SetBenchmarkResult(result);
});
}
}
}

View File

@ -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<List<BenchmarkResult>> benchmarkResult = new TaskCompletionSource<List<BenchmarkResult>>();
public static async Task<int> Main(string[] args)
{
var seleniumPort = 4444;
if (args.Length > 0)
{
if (!int.TryParse(args[0], out seleniumPort))
{
Console.Error.WriteLine("Usage Driver <selenium-port>");
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<BenchmarkResult> result)
{
benchmarkResult.TrySetResult(result);
}
private static void FormatAsBenchmarksOutput(List<BenchmarkResult> 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<BenchmarkDriverStartup>())
.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<IServer>()
.Features
.Get<IServerAddressesFeature>()
.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;
}
}
}

View File

@ -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<Uri> 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<RemoteWebDriver> 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");
}
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Intentionally pinned this to .NET Core 3.1 since that's the supported version in the docker image -->
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
<OutputType>exe</OutputType>
<!-- WebDriver is not strong-named, so this test project cannot be strong named either. -->
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<ItemGroup>
<Reference Include="Selenium.Support" />
<Reference Include="Selenium.WebDriver" />
<ProjectReference Include="..\..\..\Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj" />
<ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" />
<Content Include="appsettings.json" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,8 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

View File

@ -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 [<selenium-server-port>]` 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 <BenchmarkServerUri> --scenario blazorwasmbenchmark
```

View File

@ -3,7 +3,7 @@
using Microsoft.JSInterop;
namespace Microsoft.AspNetCore.Blazor.E2EPerformance
namespace Wasm.Performance.TestApp
{
public static class BenchmarkEvent
{

View File

@ -3,7 +3,7 @@
using Microsoft.AspNetCore.Blazor.Hosting;
namespace Microsoft.AspNetCore.Blazor.E2EPerformance
namespace Wasm.Performance.TestApp
{
public class Program
{

View File

@ -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
{

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>

View File

@ -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

View File

@ -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}`);
}
})
}

View File

@ -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: <b>${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms</b>` : '';
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 };

View File

@ -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: <b>${parseFloat(state.estimatedExecutionDurationMs.toPrecision(3))}ms</b>` : '';
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 };

View File

@ -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."
}
}
}

View File

@ -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" ]

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
/opt/bin/start-selenium-standalone.sh&
./Wasm.Performance.Driver

View File

@ -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" ]

View File

@ -38,7 +38,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Blazor\testassets\Microsoft.AspNetCore.Blazor.E2EPerformance\Microsoft.AspNetCore.Blazor.E2EPerformance.csproj" />
<ProjectReference Include="..\..\benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj" />
<ProjectReference Include="..\..\Blazor\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj" />
<ProjectReference Include="..\..\Blazor\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj" />
<ProjectReference Include="..\..\Blazor\testassets\MonoSanityClient\MonoSanityClient.csproj" />

View File

@ -13,11 +13,11 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
public class PerformanceTest
: ServerTestBase<DevHostServerFixture<Blazor.E2EPerformance.Program>>
: ServerTestBase<DevHostServerFixture<Wasm.Performance.TestApp.Program>>
{
public PerformanceTest(
BrowserFixture browserFixture,
DevHostServerFixture<Blazor.E2EPerformance.Program> serverFixture,
DevHostServerFixture<Wasm.Performance.TestApp.Program> 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
}
}
}