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
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 './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}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue