Skip to content

Commit cc2b64e

Browse files
authored
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.
1 parent 22d4dcd commit cc2b64e

19 files changed

+419
-126
lines changed

src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<!-- This is so that we add the FrameworkReference to Microsoft.AspNetCore.App -->
1414
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
1515
<MicrosoftAspNetCoreAppVersion>3.1.0</MicrosoftAspNetCoreAppVersion>
16+
<NoWarn>$(NoWarn);CS0649</NoWarn>
1617
</PropertyGroup>
1718

1819
<ItemGroup>

src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ class BenchmarkResult
88
public List<BenchmarkScenarioResult> ScenarioResults { get; set; }
99

1010
/// <summary>Downloaded application size in bytes</summary>
11-
public long DownloadSize { get; set; }
11+
public long? DownloadSize { get; set; }
12+
13+
/// <summary>WASM memory usage</summary>
14+
public long? WasmMemory { get; set; }
15+
16+
// See https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory
17+
/// <summary>JS memory usage</summary>
18+
public long? UsedJSHeapSize { get; set; }
19+
20+
/// <summary>JS memory usage</summary>
21+
public long? TotalJSHeapSize { get; set; }
1222
}
13-
}
23+
}

src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,21 @@ public static async Task<int> Main(string[] args)
2828
{
2929
// This cancellation token manages the timeout for the stress run.
3030
// By default the driver executes and reports a single Benchmark run. For stress runs,
31-
// we'll pass in the duration to execute the runs in milliseconds. This will cause this driver
31+
// we'll pass in the duration to execute the runs in seconds. This will cause this driver
3232
// to repeat executions for the duration specified.
3333
var stressRunCancellation = CancellationToken.None;
3434
var isStressRun = false;
3535
if (args.Length > 0)
3636
{
3737
if (!int.TryParse(args[0], out var stressRunSeconds))
3838
{
39-
Console.Error.WriteLine("Usage Driver <stress-run-duration>");
39+
Console.Error.WriteLine("Usage Driver <stress-run-duration-seconds>");
40+
return 1;
41+
}
42+
43+
if (stressRunSeconds < 1)
44+
{
45+
Console.Error.WriteLine("Stress run duration must be a positive integer.");
4046
return 1;
4147
}
4248

@@ -53,31 +59,30 @@ public static async Task<int> Main(string[] args)
5359
// This write is required for the benchmarking infrastructure.
5460
Console.WriteLine("Application started.");
5561

56-
using var browser = await Selenium.CreateBrowser(default);
62+
using var browser = await Selenium.CreateBrowser(default, captureBrowserMemory: isStressRun);
5763
using var testApp = StartTestApp();
5864
using var benchmarkReceiver = StartBenchmarkResultReceiver();
5965
var testAppUrl = GetListeningUrl(testApp);
66+
if (isStressRun)
67+
{
68+
testAppUrl += "/stress.html";
69+
}
70+
6071
var receiverUrl = GetListeningUrl(benchmarkReceiver);
6172
Console.WriteLine($"Test app listening at {testAppUrl}.");
6273

6374
var firstRun = true;
75+
var timeForEachRun = TimeSpan.FromMinutes(3);
76+
77+
var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated";
78+
browser.Url = launchUrl;
79+
browser.Navigate();
80+
6481
do
6582
{
6683
BenchmarkResultTask = new TaskCompletionSource<BenchmarkResult>();
67-
var timeForEachRun = TimeSpan.FromMinutes(3);
6884
using var runCancellationToken = new CancellationTokenSource(timeForEachRun);
69-
runCancellationToken.Token.Register(() => BenchmarkResultTask.TrySetException(new TimeoutException($"Timed out after {timeForEachRun}")));
70-
71-
if (firstRun)
72-
{
73-
var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated";
74-
browser.Url = launchUrl;
75-
browser.Navigate();
76-
}
77-
else
78-
{
79-
browser.FindElementById("runAll").Click();
80-
}
85+
using var registration = runCancellationToken.Token.Register(() => BenchmarkResultTask.TrySetException(new TimeoutException($"Timed out after {timeForEachRun}")));
8186

8287
var results = await BenchmarkResultTask.Task;
8388

@@ -96,21 +101,75 @@ private static void FormatAsBenchmarksOutput(BenchmarkResult benchmarkResult, bo
96101
{
97102
// Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67
98103
var output = new BenchmarkOutput();
99-
output.Metadata.Add(new BenchmarkMetadata
104+
105+
106+
if (benchmarkResult.DownloadSize != null)
100107
{
101-
Source = "BlazorWasm",
102-
Name = "blazorwasm/download-size",
103-
ShortDescription = "Download size (KB)",
104-
LongDescription = "Download size (KB)",
105-
Format = "n2",
106-
});
108+
output.Metadata.Add(new BenchmarkMetadata
109+
{
110+
Source = "BlazorWasm",
111+
Name = "blazorwasm/download-size",
112+
ShortDescription = "Download size (KB)",
113+
LongDescription = "Download size (KB)",
114+
Format = "n2",
115+
});
107116

108-
output.Measurements.Add(new BenchmarkMeasurement
117+
output.Measurements.Add(new BenchmarkMeasurement
118+
{
119+
Timestamp = DateTime.UtcNow,
120+
Name = "blazorwasm/download-size",
121+
Value = ((float)benchmarkResult.DownloadSize) / 1024,
122+
});
123+
}
124+
125+
if (benchmarkResult.WasmMemory != null)
109126
{
110-
Timestamp = DateTime.UtcNow,
111-
Name = "blazorwasm/download-size",
112-
Value = ((float)benchmarkResult.DownloadSize) / 1024,
113-
});
127+
output.Metadata.Add(new BenchmarkMetadata
128+
{
129+
Source = "BlazorWasm",
130+
Name = "blazorwasm/wasm-memory",
131+
ShortDescription = "Memory (KB)",
132+
LongDescription = "WASM reported memory (KB)",
133+
Format = "n2",
134+
});
135+
136+
output.Measurements.Add(new BenchmarkMeasurement
137+
{
138+
Timestamp = DateTime.UtcNow,
139+
Name = "blazorwasm/wasm-memory",
140+
Value = ((float)benchmarkResult.WasmMemory) / 1024,
141+
});
142+
143+
output.Metadata.Add(new BenchmarkMetadata
144+
{
145+
Source = "BlazorWasm",
146+
Name = "blazorwasm/js-usedjsheapsize",
147+
ShortDescription = "UsedJSHeapSize",
148+
LongDescription = "JS used heap size"
149+
});
150+
151+
output.Measurements.Add(new BenchmarkMeasurement
152+
{
153+
Timestamp = DateTime.UtcNow,
154+
Name = "blazorwasm/js-usedjsheapsize",
155+
Value = benchmarkResult.UsedJSHeapSize,
156+
});
157+
158+
output.Metadata.Add(new BenchmarkMetadata
159+
{
160+
Source = "BlazorWasm",
161+
Name = "blazorwasm/js-totaljsheapsize",
162+
ShortDescription = "TotalJSHeapSize",
163+
LongDescription = "JS total heap size"
164+
});
165+
166+
output.Measurements.Add(new BenchmarkMeasurement
167+
{
168+
Timestamp = DateTime.UtcNow,
169+
Name = "blazorwasm/js-totaljsheapsize",
170+
Value = benchmarkResult.TotalJSHeapSize,
171+
});
172+
}
114173

115174
// Information about the build that this was produced from
116175
output.Metadata.Add(new BenchmarkMetadata

src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private static async ValueTask<Uri> WaitForServerAsync(int port, CancellationTok
5454
throw new Exception($"Unable to connect to selenium-server at {uri}");
5555
}
5656

57-
public static async Task<RemoteWebDriver> CreateBrowser(CancellationToken cancellationToken)
57+
public static async Task<RemoteWebDriver> CreateBrowser(CancellationToken cancellationToken, bool captureBrowserMemory = false)
5858
{
5959
var uri = await WaitForServerAsync(SeleniumPort, cancellationToken);
6060

@@ -65,6 +65,11 @@ public static async Task<RemoteWebDriver> CreateBrowser(CancellationToken cancel
6565
options.AddArgument("--headless");
6666
}
6767

68+
if (captureBrowserMemory)
69+
{
70+
options.AddArgument("--enable-precise-memory-info");
71+
}
72+
6873
options.SetLoggingPreference(LogType.Browser, LogLevel.All);
6974

7075
var attempt = 0;

src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk.Web">
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading.Tasks;
5+
using Microsoft.JSInterop;
6+
7+
namespace Wasm.Performance.TestApp
8+
{
9+
public static class WasmMemory
10+
{
11+
[JSInvokable]
12+
public static long GetTotalMemory() => GC.GetTotalMemory(forceFullCollection: true);
13+
}
14+
}

src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@ new HtmlUI('E2E Performance', '#display');
1111
if (location.href.indexOf('#automated') !== -1) {
1212
(async function() {
1313
const query = new URLSearchParams(window.location.search);
14-
const group = query.get('group');
1514
const resultsUrl = query.get('resultsUrl');
1615

1716
console.log('Calculating download size...');
1817
const downloadSize = await getBlazorDownloadSize();
1918
console.log('Download size: ', downloadSize);
2019

2120
const scenarioResults = [];
22-
groups.filter(g => !group || g.name === group).forEach(g => g.runAll());
21+
groups.forEach(g => g.runAll());
2322

2423
onBenchmarkEvent(async (status, args) => {
2524
switch (status) {

src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'
22
import { BlazorApp } from './util/BlazorApp.js';
33
import { receiveEvent } from './util/BenchmarkEvents.js';
44
import { setInputValue } from './util/DOM.js';
5-
import { largeJsonToDeserialize, largeObjectToSerialize } from './jsonHandlingData.js';
5+
import { largeJsonToDeserialize, largeObjectToSerialize, benchmarkJson } from './jsonHandlingData.js';
66

77
group('JSON handling', () => {
88
let app;
@@ -71,17 +71,3 @@ group('JSON handling', () => {
7171
}
7272
});
7373
});
74-
75-
async function benchmarkJson(app, buttonSelector, resultSelector, expectedResult) {
76-
const appDocument = app.window.document;
77-
appDocument.querySelector('#reset-all').click();
78-
79-
let nextRenderCompletion = receiveEvent('Finished JSON processing');
80-
appDocument.querySelector(buttonSelector).click();
81-
await nextRenderCompletion;
82-
83-
const resultElem = appDocument.querySelector(resultSelector);
84-
if (resultElem.textContent != expectedResult.toString()) {
85-
throw new Error(`Incorrect result: ${resultElem.textContent}`);
86-
}
87-
}

src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js';
2+
import { benchmarkJson} from './jsonHandlingData.js';
3+
import { BlazorStressApp } from './util/BlazorStressApp.js';
4+
5+
group('JSON handling', () => {
6+
let app;
7+
8+
setup(() => {
9+
app = BlazorStressApp.instance;
10+
app.navigateTo('json');
11+
});
12+
13+
benchmark('Serialize 1kb', () =>
14+
benchmarkJson(app, '#serialize-small', '#serialized-length', 935), {
15+
descriptor: {
16+
name: 'blazorwasm/jsonserialize-1kb',
17+
description: 'Serialize JSON 1kb - Time in ms'
18+
}
19+
});
20+
21+
benchmark('Serialize 340kb', () =>
22+
benchmarkJson(app, '#serialize-large', '#serialized-length', 339803), {
23+
descriptor: {
24+
name: 'blazorwasm/jsonserialize-340kb',
25+
description: 'Serialize JSON 340kb - Time in ms'
26+
}
27+
});
28+
29+
benchmark('Deserialize 1kb', () =>
30+
benchmarkJson(app, '#deserialize-small', '#deserialized-count', 5), {
31+
descriptor: {
32+
name: 'blazorwasm/jsondeserialize-1kb',
33+
description: 'Deserialize JSON 1kb - Time in ms'
34+
}
35+
});
36+
37+
benchmark('Deserialize 340kb', () =>
38+
benchmarkJson(app, '#deserialize-large', '#deserialized-count', 1365), {
39+
descriptor: {
40+
name: 'blazorwasm/jsondeserialize-340kb',
41+
description: 'Deserialize JSON 340kb - Time in ms'
42+
}
43+
});
44+
});

0 commit comments

Comments
 (0)