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>. -->
|
<!-- Exclude the benchmarks because they use <PackageReference>. -->
|
||||||
<ProjectToExclude Include="
|
<ProjectToExclude Include="
|
||||||
$(RepoRoot)src\Components\benchmarkapps\**\*.csproj;
|
$(RepoRoot)src\Components\benchmarkapps\BlazingPizza.Server\**\*.csproj;
|
||||||
$(RepoRoot)src\Mvc\benchmarkapps\**\*.csproj;
|
$(RepoRoot)src\Mvc\benchmarkapps\**\*.csproj;
|
||||||
$(RepoRoot)src\Servers\Kestrel\perf\PlatformBenchmarks\**\*.csproj;
|
$(RepoRoot)src\Servers\Kestrel\perf\PlatformBenchmarks\**\*.csproj;
|
||||||
$(RepoRoot)src\SignalR\perf\benchmarkapps\**\*.csproj;
|
$(RepoRoot)src\SignalR\perf\benchmarkapps\**\*.csproj;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ namespace Wasm.Performance.Driver
|
||||||
{
|
{
|
||||||
public DateTime Timestamp { get; internal set; }
|
public DateTime Timestamp { get; internal set; }
|
||||||
public string Name { 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;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
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
|
// Statistics about publish sizes
|
||||||
output.Metadata.Add(new BenchmarkMetadata
|
output.Metadata.Add(new BenchmarkMetadata
|
||||||
{
|
{
|
||||||
|
|
@ -238,7 +257,7 @@ namespace Wasm.Performance.Driver
|
||||||
if (!directory.Exists)
|
if (!directory.Exists)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasks = new List<Task<long>>();
|
var tasks = new List<Task<long>>();
|
||||||
foreach (var item in directory.EnumerateFileSystemInfos())
|
foreach (var item in directory.EnumerateFileSystemInfos())
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Intentionally pinned this to .NET Core 3.1 since that's the supported version in the docker image -->
|
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
|
||||||
|
|
||||||
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
|
<UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference>
|
||||||
<OutputType>exe</OutputType>
|
<OutputType>exe</OutputType>
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
static string[] Clearances = new[] { "Alpha", "Beta", "Gamma", "Delta", "Epsilon" };
|
Person smallOrgChart = Person.GenerateOrgChart(1, 4);
|
||||||
Person smallOrgChart = GenerateOrgChart(1, 4);
|
Person largeOrgChart = Person.GenerateOrgChart(5, 4);
|
||||||
Person largeOrgChart = GenerateOrgChart(5, 4);
|
|
||||||
string smallOrgChartJson;
|
string smallOrgChartJson;
|
||||||
string largeOrgChartJson;
|
string largeOrgChartJson;
|
||||||
int numPeopleDeserialized;
|
int numPeopleDeserialized;
|
||||||
|
|
@ -62,23 +61,6 @@
|
||||||
void DeserializeLarge()
|
void DeserializeLarge()
|
||||||
=> numPeopleDeserialized = Deserialize(largeOrgChartJson);
|
=> 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)
|
static int Deserialize(string json)
|
||||||
{
|
{
|
||||||
var ceo = JsonSerializer.Deserialize<Person>(json);
|
var ceo = JsonSerializer.Deserialize<Person>(json);
|
||||||
|
|
@ -87,13 +69,4 @@
|
||||||
|
|
||||||
static int CountPeople(Person root)
|
static int CountPeople(Person root)
|
||||||
=> 1 + (root.Subordinates?.Sum(CountPeople) ?? 0);
|
=> 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="">Home</a> |
|
||||||
<a href="renderlist">RenderList</a> |
|
<a href="renderlist">RenderList</a> |
|
||||||
<a href="json">JSON</a>
|
<a href="json">JSON</a> |
|
||||||
|
<a href="orgchart">OrgChart</a>
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
|
|
||||||
<RazorLangVersion>3.0</RazorLangVersion>
|
<RazorLangVersion>3.0</RazorLangVersion>
|
||||||
|
|
||||||
|
<HasReferenceAssembly>false</HasReferenceAssembly>
|
||||||
|
<IsProjectReferenceProvider>false</IsProjectReferenceProvider>
|
||||||
|
<ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using Wasm.Performance.TestApp
|
@using Wasm.Performance.TestApp
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { HtmlUI } from './lib/minibench/minibench.ui.js';
|
||||||
import './appStartup.js';
|
import './appStartup.js';
|
||||||
import './renderList.js';
|
import './renderList.js';
|
||||||
import './jsonHandling.js';
|
import './jsonHandling.js';
|
||||||
|
import './orgChart.js';
|
||||||
|
|
||||||
new HtmlUI('E2E Performance', '#display');
|
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 reset --hard FETCH_HEAD \
|
||||||
&& git submodule update --init
|
&& 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
|
RUN chmod +x /app/Wasm.Performance.Driver
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue