Re-use the Blazor app for stress runs (#21078)
* Re-use the Blazor app for stress runs Our current stress runs re-used the perf tests which recreated the blazor app on each run. Perf runs are meant to run and be done, however, we want stress apps to be long lasting to capture things like memory leaks. This change creates a fork in the tests to support stress runs that re-use the app between runs.
This commit is contained in:
parent
22d4dcd353
commit
cc2b64ec5e
|
|
@ -13,6 +13,7 @@
|
|||
<!-- This is so that we add the FrameworkReference to Microsoft.AspNetCore.App -->
|
||||
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
|
||||
<MicrosoftAspNetCoreAppVersion>3.1.0</MicrosoftAspNetCoreAppVersion>
|
||||
<NoWarn>$(NoWarn);CS0649</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,16 @@ namespace Wasm.Performance.Driver
|
|||
public List<BenchmarkScenarioResult> ScenarioResults { get; set; }
|
||||
|
||||
/// <summary>Downloaded application size in bytes</summary>
|
||||
public long DownloadSize { get; set; }
|
||||
public long? DownloadSize { get; set; }
|
||||
|
||||
/// <summary>WASM memory usage</summary>
|
||||
public long? WasmMemory { get; set; }
|
||||
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory
|
||||
/// <summary>JS memory usage</summary>
|
||||
public long? UsedJSHeapSize { get; set; }
|
||||
|
||||
/// <summary>JS memory usage</summary>
|
||||
public long? TotalJSHeapSize { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace Wasm.Performance.Driver
|
|||
{
|
||||
// This cancellation token manages the timeout for the stress run.
|
||||
// By default the driver executes and reports a single Benchmark run. For stress runs,
|
||||
// we'll pass in the duration to execute the runs in milliseconds. This will cause this driver
|
||||
// we'll pass in the duration to execute the runs in seconds. This will cause this driver
|
||||
// to repeat executions for the duration specified.
|
||||
var stressRunCancellation = CancellationToken.None;
|
||||
var isStressRun = false;
|
||||
|
|
@ -36,7 +36,13 @@ namespace Wasm.Performance.Driver
|
|||
{
|
||||
if (!int.TryParse(args[0], out var stressRunSeconds))
|
||||
{
|
||||
Console.Error.WriteLine("Usage Driver <stress-run-duration>");
|
||||
Console.Error.WriteLine("Usage Driver <stress-run-duration-seconds>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (stressRunSeconds < 1)
|
||||
{
|
||||
Console.Error.WriteLine("Stress run duration must be a positive integer.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -53,31 +59,30 @@ namespace Wasm.Performance.Driver
|
|||
// This write is required for the benchmarking infrastructure.
|
||||
Console.WriteLine("Application started.");
|
||||
|
||||
using var browser = await Selenium.CreateBrowser(default);
|
||||
using var browser = await Selenium.CreateBrowser(default, captureBrowserMemory: isStressRun);
|
||||
using var testApp = StartTestApp();
|
||||
using var benchmarkReceiver = StartBenchmarkResultReceiver();
|
||||
var testAppUrl = GetListeningUrl(testApp);
|
||||
if (isStressRun)
|
||||
{
|
||||
testAppUrl += "/stress.html";
|
||||
}
|
||||
|
||||
var receiverUrl = GetListeningUrl(benchmarkReceiver);
|
||||
Console.WriteLine($"Test app listening at {testAppUrl}.");
|
||||
|
||||
var firstRun = true;
|
||||
var timeForEachRun = TimeSpan.FromMinutes(3);
|
||||
|
||||
var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated";
|
||||
browser.Url = launchUrl;
|
||||
browser.Navigate();
|
||||
|
||||
do
|
||||
{
|
||||
BenchmarkResultTask = new TaskCompletionSource<BenchmarkResult>();
|
||||
var timeForEachRun = TimeSpan.FromMinutes(3);
|
||||
using var runCancellationToken = new CancellationTokenSource(timeForEachRun);
|
||||
runCancellationToken.Token.Register(() => BenchmarkResultTask.TrySetException(new TimeoutException($"Timed out after {timeForEachRun}")));
|
||||
|
||||
if (firstRun)
|
||||
{
|
||||
var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated";
|
||||
browser.Url = launchUrl;
|
||||
browser.Navigate();
|
||||
}
|
||||
else
|
||||
{
|
||||
browser.FindElementById("runAll").Click();
|
||||
}
|
||||
using var registration = runCancellationToken.Token.Register(() => BenchmarkResultTask.TrySetException(new TimeoutException($"Timed out after {timeForEachRun}")));
|
||||
|
||||
var results = await BenchmarkResultTask.Task;
|
||||
|
||||
|
|
@ -96,21 +101,75 @@ namespace Wasm.Performance.Driver
|
|||
{
|
||||
// Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67
|
||||
var output = new BenchmarkOutput();
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
{
|
||||
Source = "BlazorWasm",
|
||||
Name = "blazorwasm/download-size",
|
||||
ShortDescription = "Download size (KB)",
|
||||
LongDescription = "Download size (KB)",
|
||||
Format = "n2",
|
||||
});
|
||||
|
||||
output.Measurements.Add(new BenchmarkMeasurement
|
||||
|
||||
if (benchmarkResult.DownloadSize != null)
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Name = "blazorwasm/download-size",
|
||||
Value = ((float)benchmarkResult.DownloadSize) / 1024,
|
||||
});
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
{
|
||||
Source = "BlazorWasm",
|
||||
Name = "blazorwasm/download-size",
|
||||
ShortDescription = "Download size (KB)",
|
||||
LongDescription = "Download size (KB)",
|
||||
Format = "n2",
|
||||
});
|
||||
|
||||
output.Measurements.Add(new BenchmarkMeasurement
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Name = "blazorwasm/download-size",
|
||||
Value = ((float)benchmarkResult.DownloadSize) / 1024,
|
||||
});
|
||||
}
|
||||
|
||||
if (benchmarkResult.WasmMemory != null)
|
||||
{
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
{
|
||||
Source = "BlazorWasm",
|
||||
Name = "blazorwasm/wasm-memory",
|
||||
ShortDescription = "Memory (KB)",
|
||||
LongDescription = "WASM reported memory (KB)",
|
||||
Format = "n2",
|
||||
});
|
||||
|
||||
output.Measurements.Add(new BenchmarkMeasurement
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Name = "blazorwasm/wasm-memory",
|
||||
Value = ((float)benchmarkResult.WasmMemory) / 1024,
|
||||
});
|
||||
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
{
|
||||
Source = "BlazorWasm",
|
||||
Name = "blazorwasm/js-usedjsheapsize",
|
||||
ShortDescription = "UsedJSHeapSize",
|
||||
LongDescription = "JS used heap size"
|
||||
});
|
||||
|
||||
output.Measurements.Add(new BenchmarkMeasurement
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Name = "blazorwasm/js-usedjsheapsize",
|
||||
Value = benchmarkResult.UsedJSHeapSize,
|
||||
});
|
||||
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
{
|
||||
Source = "BlazorWasm",
|
||||
Name = "blazorwasm/js-totaljsheapsize",
|
||||
ShortDescription = "TotalJSHeapSize",
|
||||
LongDescription = "JS total heap size"
|
||||
});
|
||||
|
||||
output.Measurements.Add(new BenchmarkMeasurement
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Name = "blazorwasm/js-totaljsheapsize",
|
||||
Value = benchmarkResult.TotalJSHeapSize,
|
||||
});
|
||||
}
|
||||
|
||||
// Information about the build that this was produced from
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ namespace Wasm.Performance.Driver
|
|||
throw new Exception($"Unable to connect to selenium-server at {uri}");
|
||||
}
|
||||
|
||||
public static async Task<RemoteWebDriver> CreateBrowser(CancellationToken cancellationToken)
|
||||
public static async Task<RemoteWebDriver> CreateBrowser(CancellationToken cancellationToken, bool captureBrowserMemory = false)
|
||||
{
|
||||
var uri = await WaitForServerAsync(SeleniumPort, cancellationToken);
|
||||
|
||||
|
|
@ -65,6 +65,11 @@ namespace Wasm.Performance.Driver
|
|||
options.AddArgument("--headless");
|
||||
}
|
||||
|
||||
if (captureBrowserMemory)
|
||||
{
|
||||
options.AddArgument("--enable-precise-memory-info");
|
||||
}
|
||||
|
||||
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
|
||||
|
||||
var attempt = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Wasm.Performance.TestApp
|
||||
{
|
||||
public static class WasmMemory
|
||||
{
|
||||
[JSInvokable]
|
||||
public static long GetTotalMemory() => GC.GetTotalMemory(forceFullCollection: true);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ new HtmlUI('E2E Performance', '#display');
|
|||
if (location.href.indexOf('#automated') !== -1) {
|
||||
(async function() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const group = query.get('group');
|
||||
const resultsUrl = query.get('resultsUrl');
|
||||
|
||||
console.log('Calculating download size...');
|
||||
|
|
@ -19,7 +18,7 @@ if (location.href.indexOf('#automated') !== -1) {
|
|||
console.log('Download size: ', downloadSize);
|
||||
|
||||
const scenarioResults = [];
|
||||
groups.filter(g => !group || g.name === group).forEach(g => g.runAll());
|
||||
groups.forEach(g => g.runAll());
|
||||
|
||||
onBenchmarkEvent(async (status, args) => {
|
||||
switch (status) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'
|
|||
import { BlazorApp } from './util/BlazorApp.js';
|
||||
import { receiveEvent } from './util/BenchmarkEvents.js';
|
||||
import { setInputValue } from './util/DOM.js';
|
||||
import { largeJsonToDeserialize, largeObjectToSerialize } from './jsonHandlingData.js';
|
||||
import { largeJsonToDeserialize, largeObjectToSerialize, benchmarkJson } from './jsonHandlingData.js';
|
||||
|
||||
group('JSON handling', () => {
|
||||
let app;
|
||||
|
|
@ -71,17 +71,3 @@ group('JSON handling', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function benchmarkJson(app, buttonSelector, resultSelector, expectedResult) {
|
||||
const appDocument = app.window.document;
|
||||
appDocument.querySelector('#reset-all').click();
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished JSON processing');
|
||||
appDocument.querySelector(buttonSelector).click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
const resultElem = appDocument.querySelector(resultSelector);
|
||||
if (resultElem.textContent != expectedResult.toString()) {
|
||||
throw new Error(`Incorrect result: ${resultElem.textContent}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,44 @@
|
|||
import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js';
|
||||
import { benchmarkJson} from './jsonHandlingData.js';
|
||||
import { BlazorStressApp } from './util/BlazorStressApp.js';
|
||||
|
||||
group('JSON handling', () => {
|
||||
let app;
|
||||
|
||||
setup(() => {
|
||||
app = BlazorStressApp.instance;
|
||||
app.navigateTo('json');
|
||||
});
|
||||
|
||||
benchmark('Serialize 1kb', () =>
|
||||
benchmarkJson(app, '#serialize-small', '#serialized-length', 935), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/jsonserialize-1kb',
|
||||
description: 'Serialize JSON 1kb - Time in ms'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('Serialize 340kb', () =>
|
||||
benchmarkJson(app, '#serialize-large', '#serialized-length', 339803), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/jsonserialize-340kb',
|
||||
description: 'Serialize JSON 340kb - Time in ms'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('Deserialize 1kb', () =>
|
||||
benchmarkJson(app, '#deserialize-small', '#deserialized-count', 5), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/jsondeserialize-1kb',
|
||||
description: 'Deserialize JSON 1kb - Time in ms'
|
||||
}
|
||||
});
|
||||
|
||||
benchmark('Deserialize 340kb', () =>
|
||||
benchmarkJson(app, '#deserialize-large', '#deserialized-count', 1365), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/jsondeserialize-340kb',
|
||||
description: 'Deserialize JSON 340kb - Time in ms'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js';
|
||||
import { BlazorApp } from './util/BlazorApp.js';
|
||||
import { receiveEvent } from './util/BenchmarkEvents.js';
|
||||
import { setInputValue } from './util/DOM.js';
|
||||
import { measureOrgChart, measureOrgChartEdit } from './orgChartBenchmark.js';
|
||||
|
||||
group('Nested components', () => {
|
||||
let app;
|
||||
|
|
@ -36,53 +35,3 @@ group('Nested components', () => {
|
|||
});
|
||||
});
|
||||
|
||||
async function measureOrgChart(app, depth, subs) {
|
||||
const appDocument = app.window.document;
|
||||
setInputValue(appDocument.querySelector('#depth'), depth.toString());
|
||||
setInputValue(appDocument.querySelector('#subs'), subs.toString());
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished OrgChart rendering');
|
||||
appDocument.querySelector('#hide').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('h2').length !== 0) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
|
||||
nextRenderCompletion = receiveEvent('Finished OrgChart rendering');
|
||||
appDocument.querySelector('#show').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('h2').length < depth * subs) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
}
|
||||
|
||||
async function measureOrgChartEdit(app, depth, subs) {
|
||||
const appDocument = app.window.document;
|
||||
setInputValue(appDocument.querySelector('#depth'), depth.toString());
|
||||
setInputValue(appDocument.querySelector('#subs'), subs.toString());
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished OrgChart rendering');
|
||||
appDocument.querySelector('#show').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
const elements = appDocument.querySelectorAll('.person');
|
||||
if (!elements) {
|
||||
throw new Error("No person elements found.");
|
||||
}
|
||||
|
||||
const personElement = elements.item(elements.length / 2);
|
||||
|
||||
const display = personElement.querySelector('.salary');
|
||||
const input = personElement.querySelector('input[type=number]');
|
||||
|
||||
nextRenderCompletion = receiveEvent('Finished PersonDisplay rendering');
|
||||
const updated = (Math.floor(Math.random() * 100000)).toString();
|
||||
setInputValue(input, updated);
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (display.innerHTML != updated) {
|
||||
throw new Error('Value not updated after render');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { receiveEvent } from './util/BenchmarkEvents.js';
|
||||
import { setInputValue } from './util/DOM.js';
|
||||
|
||||
export async function measureOrgChart(app, depth, subs) {
|
||||
const appDocument = app.window.document;
|
||||
setInputValue(appDocument.querySelector('#depth'), depth.toString());
|
||||
setInputValue(appDocument.querySelector('#subs'), subs.toString());
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished OrgChart rendering');
|
||||
appDocument.querySelector('#hide').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('h2').length !== 0) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
|
||||
nextRenderCompletion = receiveEvent('Finished OrgChart rendering');
|
||||
appDocument.querySelector('#show').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('h2').length < depth * subs) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
}
|
||||
|
||||
export async function measureOrgChartEdit(app, depth, subs) {
|
||||
const appDocument = app.window.document;
|
||||
setInputValue(appDocument.querySelector('#depth'), depth.toString());
|
||||
setInputValue(appDocument.querySelector('#subs'), subs.toString());
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished OrgChart rendering');
|
||||
appDocument.querySelector('#show').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
const elements = appDocument.querySelectorAll('.person');
|
||||
if (!elements) {
|
||||
throw new Error("No person elements found.");
|
||||
}
|
||||
|
||||
const personElement = elements.item(elements.length / 2);
|
||||
|
||||
const display = personElement.querySelector('.salary');
|
||||
const input = personElement.querySelector('input[type=number]');
|
||||
|
||||
nextRenderCompletion = receiveEvent('Finished PersonDisplay rendering');
|
||||
const updated = (Math.floor(Math.random() * 100000)).toString();
|
||||
setInputValue(input, updated);
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (display.innerHTML != updated) {
|
||||
throw new Error('Value not updated after render');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js';
|
||||
import { BlazorStressApp } from './util/BlazorStressApp.js';
|
||||
import { measureOrgChart, measureOrgChartEdit } from './orgChartBenchmark.js';
|
||||
|
||||
group('Nested components', () => {
|
||||
let app;
|
||||
|
||||
setup(() => {
|
||||
app = BlazorStressApp.instance;
|
||||
app.navigateTo('orgChart');
|
||||
});
|
||||
|
||||
benchmark('Render large nested component', () => measureOrgChart(app, 3, 3), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/orgchart-3-3-org',
|
||||
description: 'Time to render a complex component with large nesting (ms)'
|
||||
}
|
||||
});
|
||||
benchmark('Render component with edit', () => measureOrgChartEdit(app, 3, 2), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/edit-orgchart-3-2',
|
||||
description: 'Time to peform updates in a nested component (ms)'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js';
|
||||
import { BlazorApp } from './util/BlazorApp.js';
|
||||
import { receiveEvent } from './util/BenchmarkEvents.js';
|
||||
import { setInputValue } from './util/DOM.js';
|
||||
import { measureRenderList } from './renderListBenchmark.js';
|
||||
|
||||
group('Rendering list', () => {
|
||||
let app;
|
||||
|
|
@ -34,27 +33,4 @@ group('Rendering list', () => {
|
|||
description: 'Time to render 1000 item list (ms)'
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
async function measureRenderList(app, numItems) {
|
||||
const appDocument = app.window.document;
|
||||
const numItemsTextbox = appDocument.querySelector('#num-items');
|
||||
setInputValue(numItemsTextbox, numItems.toString());
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished rendering list');
|
||||
appDocument.querySelector('#hide-list').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('tbody tr').length !== 0) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
|
||||
nextRenderCompletion = receiveEvent('Finished rendering list');
|
||||
appDocument.querySelector('#show-list').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('tbody tr').length !== numItems) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { receiveEvent } from './util/BenchmarkEvents.js';
|
||||
import { setInputValue } from './util/DOM.js';
|
||||
|
||||
export async function measureRenderList(app, numItems) {
|
||||
const appDocument = app.window.document;
|
||||
const numItemsTextbox = appDocument.querySelector('#num-items');
|
||||
setInputValue(numItemsTextbox, numItems.toString());
|
||||
|
||||
let nextRenderCompletion = receiveEvent('Finished rendering list');
|
||||
appDocument.querySelector('#hide-list').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('tbody tr').length !== 0) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
|
||||
nextRenderCompletion = receiveEvent('Finished rendering list');
|
||||
appDocument.querySelector('#show-list').click();
|
||||
await nextRenderCompletion;
|
||||
|
||||
if (appDocument.querySelectorAll('tbody tr').length !== numItems) {
|
||||
throw new Error('Wrong number of items rendered');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { group, setup, benchmark } from './lib/minibench/minibench.js';
|
||||
import { BlazorStressApp } from './util/BlazorStressApp.js';
|
||||
import { measureRenderList } from './renderListBenchmark.js';
|
||||
|
||||
group('Rendering list', () => {
|
||||
let app;
|
||||
|
||||
setup(() => {
|
||||
app = BlazorStressApp.instance;
|
||||
app.navigateTo('renderList');
|
||||
});
|
||||
|
||||
benchmark('Render 10 items', () => measureRenderList(app, 10), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-10-items',
|
||||
description: 'Time to render 10 item list (ms)'
|
||||
}
|
||||
});
|
||||
benchmark('Render 100 items', () => measureRenderList(app, 100), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-100-items',
|
||||
description: 'Time to render 100 item list (ms)'
|
||||
}
|
||||
});
|
||||
benchmark('Render 1000 items', () => measureRenderList(app, 1000), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/render-1000-items',
|
||||
description: 'Time to render 1000 item list (ms)'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { groups, BenchmarkEvent, onBenchmarkEvent } from './lib/minibench/minibench.js';
|
||||
import { HtmlUI } from './lib/minibench/minibench.ui.js';
|
||||
import './renderListStress.js';
|
||||
import './jsonHandlingStress.js';
|
||||
import './orgChartStress.js';
|
||||
|
||||
import { BlazorStressApp } from './util/BlazorStressApp.js';
|
||||
|
||||
new HtmlUI('E2E Performance', '#display');
|
||||
|
||||
if (location.href.indexOf('#automated') !== -1) {
|
||||
(async function () {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const resultsUrl = query.get('resultsUrl');
|
||||
|
||||
// timeout in ms. Defaults to 2 minutes.
|
||||
const timeout = query.get('timeout') || 2 * 60 * 1000;
|
||||
const scenarioResults = [];
|
||||
|
||||
await BlazorStressApp.createAsync();
|
||||
|
||||
let shouldRun = true;
|
||||
setTimeout(() => shouldRun = false, timeout);
|
||||
|
||||
while (shouldRun) {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
onBenchmarkEvent(async (status, args) => {
|
||||
switch (status) {
|
||||
case BenchmarkEvent.runStarted:
|
||||
scenarioResults.length = 0;
|
||||
break;
|
||||
case BenchmarkEvent.benchmarkCompleted:
|
||||
case BenchmarkEvent.benchmarkError:
|
||||
console.log(`Completed benchmark ${args.name}`);
|
||||
scenarioResults.push(args);
|
||||
break;
|
||||
case BenchmarkEvent.runCompleted:
|
||||
{
|
||||
const wasmMemory = BlazorStressApp.instance.window.DotNet.invokeMethod('Wasm.Performance.TestApp', 'GetTotalMemory');
|
||||
|
||||
const jsMemory = window.performance.memory;
|
||||
|
||||
if (resultsUrl) {
|
||||
await fetch(resultsUrl, {
|
||||
method: 'post',
|
||||
body: JSON.stringify({
|
||||
wasmMemory: wasmMemory,
|
||||
usedJSHeapSize: jsMemory.usedJSHeapSize,
|
||||
totalJSHeapSize: jsMemory.totalJSHeapSize,
|
||||
scenarioResults: scenarioResults
|
||||
})
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
reject(new Error(`Unknown status: ${status}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
groups.forEach(g => g.runAll());
|
||||
await promise;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { BlazorApp } from "./BlazorApp.js";
|
||||
|
||||
export class BlazorStressApp {
|
||||
/** @returns {BlazorApp} */
|
||||
static get instance() {
|
||||
return BlazorStressApp._instance;
|
||||
}
|
||||
|
||||
/** @returns {Promise<void>} */
|
||||
static createAsync() {
|
||||
const instance = new BlazorApp();
|
||||
BlazorStressApp._instance = instance;
|
||||
|
||||
return instance.start();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>E2EPerformance</title>
|
||||
<link href="benchmarks/lib/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="benchmarks/lib/minibench/style.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body style="overflow: scroll;">
|
||||
<div class="container" id="display"></div>
|
||||
<p class="container px-3">
|
||||
<a href="blazor-frame.html">View benchmark app ⮕</a>
|
||||
</p>
|
||||
|
||||
<script type="module" src="benchmarks/stress.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue