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
{
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; }
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; }
}
/// <summary>Downloaded application size in bytes</summary>
public long DownloadSize { get; set; }
}
}

View File

@ -1,7 +1,6 @@
// 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.Collections.Generic;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@ -25,7 +24,7 @@ namespace Wasm.Performance.Driver
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,
});

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
{
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)
{
@ -57,42 +57,36 @@ namespace Wasm.Performance.Driver
browser.Url = launchUrl;
browser.Navigate();
var appSize = GetBlazorAppSize();
await Task.WhenAll(benchmarkResult.Task, appSize);
FormatAsBenchmarksOutput(benchmarkResult.Task.Result, appSize.Result);
FormatAsBenchmarksOutput(benchmarkResult.Task.Result);
Console.WriteLine("Done executing benchmark");
return 0;
}
internal static void SetBenchmarkResult(List<BenchmarkResult> result)
internal static void SetBenchmarkResult(BenchmarkResult 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
var output = new BenchmarkOutput();
foreach (var result in results)
output.Metadata.Add(new BenchmarkMetadata
{
var scenarioName = result.Descriptor.Name;
output.Metadata.Add(new BenchmarkMetadata
{
Source = "BlazorWasm",
Name = scenarioName,
ShortDescription = result.Name,
LongDescription = result.Descriptor.Description,
Format = "n2"
});
Source = "BlazorWasm",
Name = "blazorwasm/download-size",
ShortDescription = "Download size (KB)",
LongDescription = "Download size (KB)",
Format = "n2",
});
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = scenarioName,
Value = result.Duration,
});
}
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = "blazorwasm/download-size",
Value = ((float)benchmarkResult.DownloadSize) / 1024,
});
// Information about the build that this was produced from
output.Metadata.Add(new BenchmarkMetadata
@ -112,38 +106,25 @@ namespace Wasm.Performance.Driver
?.Value,
});
// Statistics about publish sizes
output.Metadata.Add(new BenchmarkMetadata
foreach (var result in benchmarkResult.ScenarioResults)
{
Source = "BlazorWasm",
Name = "blazorwasm/publish-size",
ShortDescription = "Publish size (KB)",
LongDescription = "Publish size (KB)",
Format = "n2",
});
var scenarioName = result.Descriptor.Name;
output.Metadata.Add(new BenchmarkMetadata
{
Source = "BlazorWasm",
Name = scenarioName,
ShortDescription = result.Name,
LongDescription = result.Descriptor.Description,
Format = "n2"
});
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = "blazorwasm/publish-size",
Value = sizes.publishSize / 1024,
});
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,
});
output.Measurements.Add(new BenchmarkMeasurement
{
Timestamp = DateTime.UtcNow,
Name = scenarioName,
Value = result.Duration,
});
}
Console.WriteLine("#StartJobStatistics");
Console.WriteLine(JsonSerializer.Serialize(output));
@ -156,6 +137,12 @@ namespace Wasm.Performance.Driver
{
"--urls", "http://127.0.0.1:0",
"--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);
@ -216,76 +203,5 @@ namespace Wasm.Performance.Driver
.Addresses
.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>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -17,4 +17,14 @@
<ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" />
</ItemGroup>
<Target Name="_AddTestProjectMetadataAttributes" BeforeTargets="BeforeCompile">
<ItemGroup>
<AssemblyAttribute
Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>TestAppLocatiion</_Parameter1>
<_Parameter2>$(MSBuildThisFileDirectory)..\TestApp\</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
</Target>
</Project>

View File

@ -6,6 +6,6 @@ Hello, world!
@code {
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 './jsonHandling.js';
import './orgChart.js';
import { getBlazorDownloadSize } from './blazorDownloadSize.js';
new HtmlUI('E2E Performance', '#display');
if (location.href.indexOf('#automated') !== -1) {
const query = new URLSearchParams(window.location.search);
const group = query.get('group');
const resultsUrl = query.get('resultsUrl');
(async function() {
const query = new URLSearchParams(window.location.search);
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 = [];
onBenchmarkEvent(async (status, args) => {
switch (status) {
const scenarioResults = [];
groups.filter(g => !group || g.name === group).forEach(g => g.runAll());
onBenchmarkEvent(async (status, args) => {
switch (status) {
case BenchmarkEvent.runStarted:
benchmarksResults.length = 0;
scenarioResults.length = 0;
break;
case BenchmarkEvent.benchmarkCompleted:
case BenchmarkEvent.benchmarkError:
console.log(`Completed benchmark ${args.name}`);
benchmarksResults.push(args);
scenarioResults.push(args);
break;
case BenchmarkEvent.runCompleted:
if (resultsUrl) {
await fetch(resultsUrl, {
method: 'post',
body: JSON.stringify(benchmarksResults)
});
}
break;
if (resultsUrl) {
await fetch(resultsUrl, {
method: 'post',
body: JSON.stringify({
downloadSize: downloadSize,
scenarioResults: scenarioResults
})
});
}
break;
default:
throw new Error(`Unknown status: ${status}`);
}
})
});
})();
}

View File

@ -12,7 +12,7 @@ export class BlazorApp {
async start() {
this._frame.src = 'blazor-frame.html';
await receiveEvent('Rendered index.cshtml');
await receiveEvent('Rendered Index.razor');
}
navigateTo(url) {