Add a perf scenario involing nested components + editing
* Adds additional scenarios to address https://github.com/dotnet/aspnetcore/issues/17011 * Include commit hash so we can track the build of Blazor WASM associated with a perf run * Port some infrastructure fixes from master Fixes https://github.com/dotnet/aspnetcore/issues/17011
This commit is contained in:
parent
c935e9aa2d
commit
31f63d9e72
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<!-- Exclude the benchmarks because they use <PackageReference>. -->
|
||||
<ProjectToExclude Include="
|
||||
$(RepoRoot)src\Components\benchmarkapps\**\*.csproj;
|
||||
$(RepoRoot)src\Components\benchmarkapps\BlazingPizza.Server\**\*.csproj;
|
||||
$(RepoRoot)src\Mvc\benchmarkapps\**\*.csproj;
|
||||
$(RepoRoot)src\Servers\Kestrel\perf\PlatformBenchmarks\**\*.csproj;
|
||||
$(RepoRoot)src\SignalR\perf\benchmarkapps\**\*.csproj;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ namespace Wasm.Performance.Driver
|
|||
{
|
||||
public DateTime Timestamp { get; internal set; }
|
||||
public string Name { get; internal set; }
|
||||
public double Value { get; internal set; }
|
||||
public object Value { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
|
|
@ -93,6 +94,24 @@ namespace Wasm.Performance.Driver
|
|||
});
|
||||
}
|
||||
|
||||
// Information about the build that this was produced from
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
{
|
||||
Source = "BlazorWasm",
|
||||
Name = "blazorwasm/commit",
|
||||
ShortDescription = "Commit Hash",
|
||||
});
|
||||
|
||||
output.Measurements.Add(new BenchmarkMeasurement
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Name = "blazorwasm/commit",
|
||||
Value = typeof(Program).Assembly
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.FirstOrDefault(f => f.Key == "CommitHash")
|
||||
?.Value,
|
||||
});
|
||||
|
||||
// Statistics about publish sizes
|
||||
output.Metadata.Add(new BenchmarkMetadata
|
||||
{
|
||||
|
|
@ -238,7 +257,7 @@ namespace Wasm.Performance.Driver
|
|||
if (!directory.Exists)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
var tasks = new List<Task<long>>();
|
||||
foreach (var item in directory.EnumerateFileSystemInfos())
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<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>
|
||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||
|
||||
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
|
||||
<OutputType>exe</OutputType>
|
||||
|
|
|
|||
|
|
@ -24,9 +24,8 @@
|
|||
}
|
||||
|
||||
@code {
|
||||
static string[] Clearances = new[] { "Alpha", "Beta", "Gamma", "Delta", "Epsilon" };
|
||||
Person smallOrgChart = GenerateOrgChart(1, 4);
|
||||
Person largeOrgChart = GenerateOrgChart(5, 4);
|
||||
Person smallOrgChart = Person.GenerateOrgChart(1, 4);
|
||||
Person largeOrgChart = Person.GenerateOrgChart(5, 4);
|
||||
string smallOrgChartJson;
|
||||
string largeOrgChartJson;
|
||||
int numPeopleDeserialized;
|
||||
|
|
@ -62,23 +61,6 @@
|
|||
void DeserializeLarge()
|
||||
=> numPeopleDeserialized = Deserialize(largeOrgChartJson);
|
||||
|
||||
static Person GenerateOrgChart(int totalDepth, int numDescendantsPerNode, int thisDepth = 0, string namePrefix = null, int siblingIndex = 0)
|
||||
{
|
||||
var name = $"{namePrefix ?? "CEO"} - Subordinate {siblingIndex}";
|
||||
var rng = new Random(0);
|
||||
return new Person
|
||||
{
|
||||
Name = name,
|
||||
IsAdmin = siblingIndex % 2 == 0,
|
||||
Salary = 10000000 / (thisDepth + 1),
|
||||
SecurityClearances = Clearances
|
||||
.ToDictionary(c => c, _ => (object)(rng.Next(0, 2) == 0)),
|
||||
Subordinates = Enumerable.Range(0, thisDepth < totalDepth ? numDescendantsPerNode : 0)
|
||||
.Select(index => GenerateOrgChart(totalDepth, numDescendantsPerNode, thisDepth + 1, name, index))
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
static int Deserialize(string json)
|
||||
{
|
||||
var ceo = JsonSerializer.Deserialize<Person>(json);
|
||||
|
|
@ -87,13 +69,4 @@
|
|||
|
||||
static int CountPeople(Person root)
|
||||
=> 1 + (root.Subordinates?.Sum(CountPeople) ?? 0);
|
||||
|
||||
class Person
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int Salary { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
public List<Person> Subordinates { get; set; }
|
||||
public Dictionary<string, object> SecurityClearances { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
@page "/orgchart"
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<h1>Org Chart</h1>
|
||||
<fieldset>
|
||||
<label>Depth: <input id="depth" type="number" @bind="depth" /></label>
|
||||
<label>Subordinates: <input id="subs" type="number" @bind="subs" /></label>
|
||||
|
||||
<button id="show" @onclick="Show">Show</button>
|
||||
<button id="hide" @onclick="Hide">Hide</button>
|
||||
</fieldset>
|
||||
|
||||
@if (show)
|
||||
{
|
||||
<PersonDisplay Person="Person.GenerateOrgChart(depth, subs)" />
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
int depth = 2;
|
||||
int subs = 5;
|
||||
bool show;
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
BenchmarkEvent.Send(JSRuntime, "Finished OrgChart rendering");
|
||||
}
|
||||
|
||||
void Hide()
|
||||
{
|
||||
show = false;
|
||||
}
|
||||
|
||||
void Show()
|
||||
{
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<div class="person">
|
||||
<h2>
|
||||
@Person.Name
|
||||
@if (Person.IsAdmin)
|
||||
{
|
||||
<span>[Administrator]</span>
|
||||
}
|
||||
</h2>
|
||||
|
||||
Salary: $<h3 class="salary">@Person.Salary</h3>
|
||||
|
||||
<EditForm Model="Person">
|
||||
<div>
|
||||
<label>Salary</label>
|
||||
<InputNumber @bind-Value="Person.Salary" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Adminstrator: </label>
|
||||
<InputCheckbox @bind-Value="Person.IsAdmin" />
|
||||
</div>
|
||||
</EditForm>
|
||||
|
||||
<ul>
|
||||
@foreach (var kvp in Person.SecurityClearances)
|
||||
{
|
||||
<li>@kvp.Key: @kvp.Value</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@foreach (var person in Person.Subordinates)
|
||||
{
|
||||
<ul>
|
||||
<li>
|
||||
<PersonDisplay Person="person" />
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Person Person { get; set; }
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
BenchmarkEvent.Send(JSRuntime, "Finished PersonDisplay rendering");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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.Linq;
|
||||
|
||||
namespace Wasm.Performance.TestApp
|
||||
{
|
||||
public class Person
|
||||
{
|
||||
static readonly string[] Clearances = new[] { "Alpha", "Beta", "Gamma", "Delta", "Epsilon" };
|
||||
|
||||
public string Name { get; set; }
|
||||
public int Salary { get; set; }
|
||||
public bool IsAdmin { get; set; }
|
||||
public List<Person> Subordinates { get; set; }
|
||||
public Dictionary<string, object> SecurityClearances { get; set; }
|
||||
|
||||
public static Person GenerateOrgChart(int totalDepth, int numDescendantsPerNode, int thisDepth = 0, string namePrefix = null, int siblingIndex = 0)
|
||||
{
|
||||
|
||||
var name = $"{namePrefix ?? "CEO"} - Subordinate {siblingIndex}";
|
||||
var rng = new Random(0);
|
||||
return new Person
|
||||
{
|
||||
Name = name,
|
||||
IsAdmin = siblingIndex % 2 == 0,
|
||||
Salary = 10000000 / (thisDepth + 1),
|
||||
SecurityClearances = Clearances
|
||||
.ToDictionary(c => c, _ => (object)(rng.Next(0, 2) == 0)),
|
||||
Subordinates = Enumerable.Range(0, thisDepth < totalDepth ? numDescendantsPerNode : 0)
|
||||
.Select(index => GenerateOrgChart(totalDepth, numDescendantsPerNode, thisDepth + 1, name, index))
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
<a href="">Home</a> |
|
||||
<a href="renderlist">RenderList</a> |
|
||||
<a href="json">JSON</a>
|
||||
<a href="json">JSON</a> |
|
||||
<a href="orgchart">OrgChart</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
|
||||
<RazorLangVersion>3.0</RazorLangVersion>
|
||||
|
||||
<HasReferenceAssembly>false</HasReferenceAssembly>
|
||||
<IsProjectReferenceProvider>false</IsProjectReferenceProvider>
|
||||
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
@using System.Net.Http
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.JSInterop
|
||||
@using Wasm.Performance.TestApp
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { HtmlUI } from './lib/minibench/minibench.ui.js';
|
|||
import './appStartup.js';
|
||||
import './renderList.js';
|
||||
import './jsonHandling.js';
|
||||
import './orgChart.js';
|
||||
|
||||
new HtmlUI('E2E Performance', '#display');
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
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';
|
||||
|
||||
group('Nested components', () => {
|
||||
let app;
|
||||
|
||||
setup(async () => {
|
||||
app = new BlazorApp();
|
||||
await app.start();
|
||||
app.navigateTo('orgChart');
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
app.dispose();
|
||||
});
|
||||
|
||||
benchmark('Render small nested component', () => measureOrgChart(app, 1, 4), {
|
||||
descriptor: {
|
||||
name: 'blazorwasm/orgchart-1-4-org',
|
||||
description: 'Time to render a complex component with small nesting (ms)'
|
||||
}
|
||||
});
|
||||
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)'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,8 @@ RUN git init \
|
|||
&& 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 ./restore.sh
|
||||
RUN .dotnet/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
|
||||
|
|
|
|||
Loading…
Reference in New Issue