Add a benchmarking app for Blazor Wasm

This commit is contained in:
Pranav K 2019-12-10 15:33:45 -08:00
parent 2e2d0625e1
commit 17b1dc9841
No known key found for this signature in database
GPG Key ID: 1963DA6D96C3057A
48 changed files with 821 additions and 213 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,35 @@
// 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.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 request =>
{
var result = await JsonSerializer.DeserializeAsync<List<BenchmarkResult>>(request.Request.Body, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
Program.SetBenchmarkResult(result);
});
}
}
}

View File

@ -0,0 +1,210 @@
// 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.Diagnostics;
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;
}
}
using var browser = await Selenium.CreateBrowser(seleniumPort);
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 cancellationToken = new CancellationTokenSource(Timeout);
cancellationToken.Token.Register(() => benchmarkResult.TrySetException(new TimeoutException($"Timed out after {Timeout}")));
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 (linked)",
ShortDescription = "Publish size - linked app (MB)",
LongDescription = "Publish size - linked app (MB)",
Format = "n2",
});
var testAssembly = typeof(TestApp.Startup).Assembly;
var testApp = new DirectoryInfo(Path.Combine(
Path.GetDirectoryName(testAssembly.Location),
testAssembly.GetName().Name));
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = "Publish size (linked)",
Value = GetDirectorySize(testApp) / 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,99 @@
// 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.Net;
using System.Net.Http;
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;
private static async ValueTask<Uri> WaitForServerAsync(int port)
{
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")).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)
{
var uri = await WaitForServerAsync(port);
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);
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,18 @@
## 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 speaks the Benchmark protocol. You generally do not need to run the Driver locally, but if you were to do so, you require docker. Here are the commands you would need to run it locally:
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 --services.blazorwasmbenchmark.endpoints <BenchmarkServerUri>
```

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,38 @@
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:
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,15 @@
{
"dependencies": [
"blazorwasmbenchmark"
],
"services": {
"blazorwasmbenchmark": {
"source": {
"repository": "https://github.com/aspnet/AspNetCore.git",
"branchOrCommit": "prkrishn/blazor-benchmarking",
"dockerfile": "src/Components/benchmarkapps/Wasm.Performance/dockerfile"
},
"waitForExit": true
}
}
}

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=prkrishn/blazor-benchmarking
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 ./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)
{