Use browser APIs to calculate Blazor's download size (#19547)
This commit is contained in:
parent
e15e1c2415
commit
f4446f373f
|
|
@ -1,24 +1,13 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Wasm.Performance.Driver
|
namespace Wasm.Performance.Driver
|
||||||
{
|
{
|
||||||
class BenchmarkResult
|
class BenchmarkResult
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
/// <summary>The result of executing scenario benchmarks</summary>
|
||||||
|
public List<BenchmarkScenarioResult> ScenarioResults { get; set; }
|
||||||
|
|
||||||
public BenchmarkDescriptor Descriptor { get; set; }
|
/// <summary>Downloaded application size in bytes</summary>
|
||||||
|
public long DownloadSize { get; set; }
|
||||||
public string ShortDescription { get; set; }
|
|
||||||
|
|
||||||
public bool Success { get; set; }
|
|
||||||
|
|
||||||
public int NumExecutions { get; set; }
|
|
||||||
|
|
||||||
public double Duration { get; set; }
|
|
||||||
|
|
||||||
public class BenchmarkDescriptor
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
public string Description { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// 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 System.Text.Json;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
@ -25,7 +24,7 @@ namespace Wasm.Performance.Driver
|
||||||
|
|
||||||
app.Run(async context =>
|
app.Run(async context =>
|
||||||
{
|
{
|
||||||
var result = await JsonSerializer.DeserializeAsync<List<BenchmarkResult>>(context.Request.Body, new JsonSerializerOptions
|
var result = await JsonSerializer.DeserializeAsync<BenchmarkResult>(context.Request.Body, new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
namespace Wasm.Performance.Driver
|
||||||
|
{
|
||||||
|
class BenchmarkScenarioResult
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public BenchmarkDescriptor Descriptor { get; set; }
|
||||||
|
|
||||||
|
public string ShortDescription { get; set; }
|
||||||
|
|
||||||
|
public bool Success { get; set; }
|
||||||
|
|
||||||
|
public int NumExecutions { get; set; }
|
||||||
|
|
||||||
|
public double Duration { get; set; }
|
||||||
|
|
||||||
|
public class BenchmarkDescriptor
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,7 @@ namespace Wasm.Performance.Driver
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3);
|
static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3);
|
||||||
static TaskCompletionSource<List<BenchmarkResult>> benchmarkResult = new TaskCompletionSource<List<BenchmarkResult>>();
|
static TaskCompletionSource<BenchmarkResult> benchmarkResult = new TaskCompletionSource<BenchmarkResult>();
|
||||||
|
|
||||||
public static async Task<int> Main(string[] args)
|
public static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
|
|
@ -57,42 +57,36 @@ namespace Wasm.Performance.Driver
|
||||||
browser.Url = launchUrl;
|
browser.Url = launchUrl;
|
||||||
browser.Navigate();
|
browser.Navigate();
|
||||||
|
|
||||||
var appSize = GetBlazorAppSize();
|
FormatAsBenchmarksOutput(benchmarkResult.Task.Result);
|
||||||
await Task.WhenAll(benchmarkResult.Task, appSize);
|
|
||||||
FormatAsBenchmarksOutput(benchmarkResult.Task.Result, appSize.Result);
|
|
||||||
|
|
||||||
Console.WriteLine("Done executing benchmark");
|
Console.WriteLine("Done executing benchmark");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void SetBenchmarkResult(List<BenchmarkResult> result)
|
internal static void SetBenchmarkResult(BenchmarkResult result)
|
||||||
{
|
{
|
||||||
benchmarkResult.TrySetResult(result);
|
benchmarkResult.TrySetResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FormatAsBenchmarksOutput(List<BenchmarkResult> results, (long publishSize, long compressedSize) sizes)
|
private static void FormatAsBenchmarksOutput(BenchmarkResult benchmarkResult)
|
||||||
{
|
{
|
||||||
// Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67
|
// Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67
|
||||||
var output = new BenchmarkOutput();
|
var output = new BenchmarkOutput();
|
||||||
foreach (var result in results)
|
output.Metadata.Add(new BenchmarkMetadata
|
||||||
{
|
{
|
||||||
var scenarioName = result.Descriptor.Name;
|
Source = "BlazorWasm",
|
||||||
output.Metadata.Add(new BenchmarkMetadata
|
Name = "blazorwasm/download-size",
|
||||||
{
|
ShortDescription = "Download size (KB)",
|
||||||
Source = "BlazorWasm",
|
LongDescription = "Download size (KB)",
|
||||||
Name = scenarioName,
|
Format = "n2",
|
||||||
ShortDescription = result.Name,
|
});
|
||||||
LongDescription = result.Descriptor.Description,
|
|
||||||
Format = "n2"
|
|
||||||
});
|
|
||||||
|
|
||||||
output.Measurements.Add(new BenchmarkMeasurement
|
output.Measurements.Add(new BenchmarkMeasurement
|
||||||
{
|
{
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
Name = scenarioName,
|
Name = "blazorwasm/download-size",
|
||||||
Value = result.Duration,
|
Value = ((float)benchmarkResult.DownloadSize) / 1024,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Information about the build that this was produced from
|
// Information about the build that this was produced from
|
||||||
output.Metadata.Add(new BenchmarkMetadata
|
output.Metadata.Add(new BenchmarkMetadata
|
||||||
|
|
@ -112,38 +106,25 @@ namespace Wasm.Performance.Driver
|
||||||
?.Value,
|
?.Value,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Statistics about publish sizes
|
foreach (var result in benchmarkResult.ScenarioResults)
|
||||||
output.Metadata.Add(new BenchmarkMetadata
|
|
||||||
{
|
{
|
||||||
Source = "BlazorWasm",
|
var scenarioName = result.Descriptor.Name;
|
||||||
Name = "blazorwasm/publish-size",
|
output.Metadata.Add(new BenchmarkMetadata
|
||||||
ShortDescription = "Publish size (KB)",
|
{
|
||||||
LongDescription = "Publish size (KB)",
|
Source = "BlazorWasm",
|
||||||
Format = "n2",
|
Name = scenarioName,
|
||||||
});
|
ShortDescription = result.Name,
|
||||||
|
LongDescription = result.Descriptor.Description,
|
||||||
|
Format = "n2"
|
||||||
|
});
|
||||||
|
|
||||||
output.Measurements.Add(new BenchmarkMeasurement
|
output.Measurements.Add(new BenchmarkMeasurement
|
||||||
{
|
{
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
Name = "blazorwasm/publish-size",
|
Name = scenarioName,
|
||||||
Value = sizes.publishSize / 1024,
|
Value = result.Duration,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
output.Metadata.Add(new BenchmarkMetadata
|
|
||||||
{
|
|
||||||
Source = "BlazorWasm",
|
|
||||||
Name = "blazorwasm/compressed-publish-size",
|
|
||||||
ShortDescription = "Publish size compressed app (KB)",
|
|
||||||
LongDescription = "Publish size - compressed app (KB)",
|
|
||||||
Format = "n2",
|
|
||||||
});
|
|
||||||
|
|
||||||
output.Measurements.Add(new BenchmarkMeasurement
|
|
||||||
{
|
|
||||||
Timestamp = DateTime.UtcNow,
|
|
||||||
Name = "blazorwasm/compressed-publish-size",
|
|
||||||
Value = sizes.compressedSize / 1024,
|
|
||||||
});
|
|
||||||
|
|
||||||
Console.WriteLine("#StartJobStatistics");
|
Console.WriteLine("#StartJobStatistics");
|
||||||
Console.WriteLine(JsonSerializer.Serialize(output));
|
Console.WriteLine(JsonSerializer.Serialize(output));
|
||||||
|
|
@ -156,6 +137,12 @@ namespace Wasm.Performance.Driver
|
||||||
{
|
{
|
||||||
"--urls", "http://127.0.0.1:0",
|
"--urls", "http://127.0.0.1:0",
|
||||||
"--applicationpath", typeof(TestApp.Program).Assembly.Location,
|
"--applicationpath", typeof(TestApp.Program).Assembly.Location,
|
||||||
|
#if DEBUG
|
||||||
|
"--contentroot",
|
||||||
|
Path.GetFullPath(typeof(Program).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||||
|
.First(f => f.Key == "TestAppLocatiion")
|
||||||
|
.Value)
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
var host = DevHostServerProgram.BuildWebHost(args);
|
var host = DevHostServerProgram.BuildWebHost(args);
|
||||||
|
|
@ -216,76 +203,5 @@ namespace Wasm.Performance.Driver
|
||||||
.Addresses
|
.Addresses
|
||||||
.First();
|
.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task<(long size, long compressedSize)> GetBlazorAppSize()
|
|
||||||
{
|
|
||||||
var testAssembly = typeof(TestApp.Program).Assembly;
|
|
||||||
var testAssemblyLocation = new FileInfo(testAssembly.Location);
|
|
||||||
var testApp = new DirectoryInfo(Path.Combine(
|
|
||||||
testAssemblyLocation.Directory.FullName,
|
|
||||||
testAssembly.GetName().Name));
|
|
||||||
|
|
||||||
return (GetDirectorySize(testApp), await GetBrotliCompressedSize(testApp));
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async Task<long> GetBrotliCompressedSize(DirectoryInfo directory)
|
|
||||||
{
|
|
||||||
if (!directory.Exists)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tasks = new List<Task<long>>();
|
|
||||||
foreach (var item in directory.EnumerateFileSystemInfos())
|
|
||||||
{
|
|
||||||
if (item is FileInfo fileInfo)
|
|
||||||
{
|
|
||||||
tasks.Add(GetCompressedFileSize(fileInfo));
|
|
||||||
}
|
|
||||||
else if (item is DirectoryInfo directoryInfo)
|
|
||||||
{
|
|
||||||
tasks.Add(GetBrotliCompressedSize(directoryInfo));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await Task.WhenAll(tasks)).Sum(s => s);
|
|
||||||
|
|
||||||
async Task<long> GetCompressedFileSize(FileInfo fileInfo)
|
|
||||||
{
|
|
||||||
using var inputStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, 1, useAsync: true);
|
|
||||||
using var outputStream = new MemoryStream();
|
|
||||||
|
|
||||||
using (var brotliStream = new BrotliStream(outputStream, CompressionLevel.Optimal, leaveOpen: true))
|
|
||||||
{
|
|
||||||
await inputStream.CopyToAsync(brotliStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputStream.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||||
|
|
@ -17,4 +17,14 @@
|
||||||
<ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" />
|
<ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="_AddTestProjectMetadataAttributes" BeforeTargets="BeforeCompile">
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute
|
||||||
|
Include="System.Reflection.AssemblyMetadataAttribute">
|
||||||
|
<_Parameter1>TestAppLocatiion</_Parameter1>
|
||||||
|
<_Parameter2>$(MSBuildThisFileDirectory)..\TestApp\</_Parameter2>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,6 @@ Hello, world!
|
||||||
@code {
|
@code {
|
||||||
protected override void OnAfterRender(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
{
|
||||||
BenchmarkEvent.Send(JSRuntime, "Rendered index.cshtml");
|
BenchmarkEvent.Send(JSRuntime, "Rendered Index.razor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { BlazorApp } from './util/BlazorApp.js';
|
||||||
|
|
||||||
|
export async function getBlazorDownloadSize() {
|
||||||
|
// Clear caches
|
||||||
|
for (var key of await caches.keys()) {
|
||||||
|
await caches.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new BlazorApp();
|
||||||
|
try {
|
||||||
|
await app.start();
|
||||||
|
const downloadSize = app.window.performance.getEntries().reduce((prev, next) => (next.encodedBodySize || 0) + prev, 0);
|
||||||
|
return downloadSize;
|
||||||
|
} finally {
|
||||||
|
app.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,37 +4,47 @@ import './appStartup.js';
|
||||||
import './renderList.js';
|
import './renderList.js';
|
||||||
import './jsonHandling.js';
|
import './jsonHandling.js';
|
||||||
import './orgChart.js';
|
import './orgChart.js';
|
||||||
|
import { getBlazorDownloadSize } from './blazorDownloadSize.js';
|
||||||
|
|
||||||
new HtmlUI('E2E Performance', '#display');
|
new HtmlUI('E2E Performance', '#display');
|
||||||
|
|
||||||
if (location.href.indexOf('#automated') !== -1) {
|
if (location.href.indexOf('#automated') !== -1) {
|
||||||
const query = new URLSearchParams(window.location.search);
|
(async function() {
|
||||||
const group = query.get('group');
|
const query = new URLSearchParams(window.location.search);
|
||||||
const resultsUrl = query.get('resultsUrl');
|
const group = query.get('group');
|
||||||
|
const resultsUrl = query.get('resultsUrl');
|
||||||
|
|
||||||
groups.filter(g => !group || g.name === group).forEach(g => g.runAll());
|
console.log('Calculating download size...');
|
||||||
|
const downloadSize = await getBlazorDownloadSize();
|
||||||
|
console.log('Download size: ', downloadSize);
|
||||||
|
|
||||||
const benchmarksResults = [];
|
const scenarioResults = [];
|
||||||
onBenchmarkEvent(async (status, args) => {
|
groups.filter(g => !group || g.name === group).forEach(g => g.runAll());
|
||||||
switch (status) {
|
|
||||||
|
onBenchmarkEvent(async (status, args) => {
|
||||||
|
switch (status) {
|
||||||
case BenchmarkEvent.runStarted:
|
case BenchmarkEvent.runStarted:
|
||||||
benchmarksResults.length = 0;
|
scenarioResults.length = 0;
|
||||||
break;
|
break;
|
||||||
case BenchmarkEvent.benchmarkCompleted:
|
case BenchmarkEvent.benchmarkCompleted:
|
||||||
case BenchmarkEvent.benchmarkError:
|
case BenchmarkEvent.benchmarkError:
|
||||||
console.log(`Completed benchmark ${args.name}`);
|
console.log(`Completed benchmark ${args.name}`);
|
||||||
benchmarksResults.push(args);
|
scenarioResults.push(args);
|
||||||
break;
|
break;
|
||||||
case BenchmarkEvent.runCompleted:
|
case BenchmarkEvent.runCompleted:
|
||||||
if (resultsUrl) {
|
if (resultsUrl) {
|
||||||
await fetch(resultsUrl, {
|
await fetch(resultsUrl, {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: JSON.stringify(benchmarksResults)
|
body: JSON.stringify({
|
||||||
});
|
downloadSize: downloadSize,
|
||||||
}
|
scenarioResults: scenarioResults
|
||||||
break;
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown status: ${status}`);
|
throw new Error(`Unknown status: ${status}`);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export class BlazorApp {
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
this._frame.src = 'blazor-frame.html';
|
this._frame.src = 'blazor-frame.html';
|
||||||
await receiveEvent('Rendered index.cshtml');
|
await receiveEvent('Rendered Index.razor');
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateTo(url) {
|
navigateTo(url) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue