Use browser APIs to calculate Blazor's download size (#19547)

This commit is contained in:
Pranav K 2020-03-05 06:38:35 -08:00 committed by GitHub
parent e15e1c2415
commit f4446f373f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 163 deletions

View File

@ -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; }
}
} }
} }

View File

@ -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,
}); });

View File

@ -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; }
}
}
}

View File

@ -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;
}
}
} }
} }

View File

@ -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>

View File

@ -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");
} }
} }

View File

@ -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();
}
}

View File

@ -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}`);
} }
}) });
})();
} }

View File

@ -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) {