Skip to content

Commit

Permalink
[wasm] Add AppStart task to the bench Sample (#61481)
Browse files Browse the repository at this point in the history
Measure browser app start times, 2 measurements implemented.

First to measure till the JS window.pageshow event, second to measure
time when we reach managed C# code.

Example ouput:

    | measurement | time |
    |-:|-:|
    |                    AppStart, Page show |   108.1400ms |
    |                AppStart, Reach managed |   240.2174ms |
  • Loading branch information
radekdoulik authored Nov 12, 2021
1 parent 5fa6dd3 commit 35704e4
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 17 deletions.
88 changes: 88 additions & 0 deletions src/mono/sample/wasm/browser-bench/AppStart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading.Tasks;

namespace Sample
{
public class AppStartTask : BenchTask
{
public override string Name => "AppStart";
public override bool BrowserOnly => true;

[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, "System.Runtime.InteropServices.JavaScript.Runtime", "System.Private.Runtime.InteropServices.JavaScript")]
static Type jsRuntimeType = System.Type.GetType("System.Runtime.InteropServices.JavaScript.Runtime, System.Private.Runtime.InteropServices.JavaScript", true);
static Type jsFunctionType = System.Type.GetType("System.Runtime.InteropServices.JavaScript.Function, System.Private.Runtime.InteropServices.JavaScript", true);
[DynamicDependency("InvokeJS(System.String)", "System.Runtime.InteropServices.JavaScript.Runtime", "System.Private.Runtime.InteropServices.JavaScript")]
static MethodInfo invokeJSMethod = jsRuntimeType.GetMethod("InvokeJS", new Type[] { typeof(string) });
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, "System.Runtime.InteropServices.JavaScript.Function", "System.Private.Runtime.InteropServices.JavaScript")]
static ConstructorInfo functionConstructor = jsRuntimeType.GetConstructor(new Type[] { typeof(object[]) });
[DynamicDependency("Call()", "System.Runtime.InteropServices.JavaScript.Function", "System.Private.Runtime.InteropServices.JavaScript")]
static MethodInfo functionCall = jsFunctionType.GetMethod("Call", BindingFlags.Instance | BindingFlags.Public, new Type[] { });

public AppStartTask()
{
measurements = new Measurement[] {
new PageShow(),
new ReachManaged(),
};
}

Measurement[] measurements;
public override Measurement[] Measurements => measurements;

static string InvokeJS(string js)
{
return (string)invokeJSMethod.Invoke(null, new object[] { js });
}

class PageShow : BenchTask.Measurement
{
public override string Name => "Page show";

public override int InitialSamples => 3;

async Task RunAsyncStep()
{
var function = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.StartAppUI();" } });
var task = (Task<object>)functionCall.Invoke(function, new object[] { });

await task;
}

public override bool HasRunStepAsync => true;

public override async Task RunStepAsync()
{
var function = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.PageShow();" } });
await (Task<object>)functionCall.Invoke(function, null);
}
}

class ReachManaged : BenchTask.Measurement
{
public override string Name => "Reach managed";
public override int InitialSamples => 3;
public override bool HasRunStepAsync => true;

static object jsUIReachedManagedFunction = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.ReachedManaged();" } });
static object jsReached = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.reached();" } });

[MethodImpl(MethodImplOptions.NoInlining)]
public static void Reached()
{
functionCall.Invoke(jsReached, null);
}

public override async Task RunStepAsync()
{
await (Task<object>)functionCall.Invoke(jsUIReachedManagedFunction, null);
}
}
}
}
27 changes: 20 additions & 7 deletions src/mono/sample/wasm/browser-bench/BenchTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using System.Text.RegularExpressions;
using System.Threading.Tasks;

abstract class BenchTask
public abstract class BenchTask
{
public abstract string Name { get; }
readonly List<Result> results = new();
Expand All @@ -18,7 +18,7 @@ public async Task<string> RunBatch(List<Result> results, int measurementIdx, int
{
var measurement = Measurements[measurementIdx];
await measurement.BeforeBatch();
var result = measurement.RunBatch(this, milliseconds);
var result = await measurement.RunBatch(this, milliseconds);
results.Add(result);
await measurement.AfterBatch();

Expand Down Expand Up @@ -50,27 +50,37 @@ public abstract class Measurement

public virtual Task AfterBatch() { return Task.CompletedTask; }

public abstract void RunStep();
public virtual void RunStep() { }
public virtual async Task RunStepAsync() { await Task.CompletedTask; }

public virtual bool HasRunStepAsync => false;

protected virtual int CalculateSteps(int milliseconds, TimeSpan initTs)
{
return (int)(milliseconds * InitialSamples / Math.Max(1.0, initTs.TotalMilliseconds));
}

public Result RunBatch(BenchTask task, int milliseconds)
public async Task<Result> RunBatch(BenchTask task, int milliseconds)
{
DateTime start = DateTime.Now;
DateTime end;
int i = 0;
try
{
// run one to eliminate possible startup overhead and do GC collection
RunStep();
if (HasRunStepAsync)
await RunStepAsync();
else
RunStep();

GC.Collect();

start = DateTime.Now;
for (i = 0; i < InitialSamples; i++)
RunStep();
if (HasRunStepAsync)
await RunStepAsync();
else
RunStep();
end = DateTime.Now;

var initTs = end - start;
Expand All @@ -79,7 +89,10 @@ public Result RunBatch(BenchTask task, int milliseconds)
start = DateTime.Now;
for (i = 0; i < steps; i++)
{
RunStep();
if (HasRunStepAsync)
await RunStepAsync();
else
RunStep();
}
end = DateTime.Now;

Expand Down
3 changes: 2 additions & 1 deletion src/mono/sample/wasm/browser-bench/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ public partial class Test
{
List<BenchTask> tasks = new()
{
new AppStartTask(),
new ExceptionsTask(),
new JsonTask (),
new JsonTask(),
new WebSocketTask()
};
static Test instance = new Test();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
<!-- don't need to run this on helix -->
<WasmCopyAppZipToHelixTestDir>false</WasmCopyAppZipToHelixTestDir>
<WasmMainJSPath>runtime.js</WasmMainJSPath>
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
</PropertyGroup>

<ItemGroup>
<WasmExtraFilesToDeploy Include="index.html" />
<WasmExtraFilesToDeploy Include="appstart-frame.html" />
<WasmExtraFilesToDeploy Include="appstart.js" />
<WasmExtraFilesToDeploy Include="style.css" />
<Compile Remove="Console/Console.cs" />
</ItemGroup>

Expand Down
41 changes: 41 additions & 0 deletions src/mono/sample/wasm/browser-bench/appstart-frame.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>
<head>
<title>App task</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h3 id="header">Wasm Browser Sample - App task frame</h3>
<span id="out"></span>
<script type='text/javascript'>
var test_exit = function(exit_code)
{
/* Set result in a tests_done element, to be read by xharness */
var tests_done_elem = document.createElement("label");
tests_done_elem.id = "tests_done";
tests_done_elem.innerHTML = exit_code.toString();
document.body.appendChild(tests_done_elem);

console.log(`WASM EXIT ${exit_code}`);
};

window.addEventListener("pageshow", event => { window.parent.resolveAppStartEvent(event); })

var App = {
init: function () {
INTERNAL.call_static_method("[Wasm.Browser.Bench.Sample] Sample.AppStartTask/ReachManaged:Reached");
},

reached: function() {
window.parent.resolveAppStartEvent("reached");
}
};

</script>
<script type="text/javascript" src="runtime.js"></script>
<script defer src="dotnet.js"></script>
</body>
</html>
31 changes: 31 additions & 0 deletions src/mono/sample/wasm/browser-bench/appstart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var AppStart = {
Construct: function() {
this._frame = document.createElement('iframe');
document.body.appendChild(this._frame);
},

WaitForPageShow: async function() {
let promise;
let promiseResolve;
this._frame.src = 'appstart-frame.html';
promise = new Promise(resolve => { promiseResolve = resolve; })
window.resolveAppStartEvent = function(event) { promiseResolve(); }
await promise;
},

WaitForReached: async function() {
let promise;
let promiseResolve;
this._frame.src = 'appstart-frame.html';
promise = new Promise(resolve => { promiseResolve = resolve; })
window.resolveAppStartEvent = function(event) {
if (event == "reached")
promiseResolve();
}
await promise;
},

RemoveFrame: function () {
document.body.removeChild(this._frame);
}
};
22 changes: 22 additions & 0 deletions src/mono/sample/wasm/browser-bench/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<title>TESTS</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
</head>
<body onload="onLoad()">
<h3 id="header">Wasm Browser Sample - Simple Benchmark</h3>
Expand Down Expand Up @@ -53,10 +54,31 @@ <h3 id="header">Wasm Browser Sample - Simple Benchmark</h3>
if (tasks != '')
INTERNAL.call_static_method("[Wasm.Browser.Bench.Sample] Sample.Test:SetTasks", tasks);
yieldBench ();
},

PageShow: async function ()
{
AppStart.Construct();
try {
await AppStart.WaitForPageShow();
} finally {
AppStart.RemoveFrame();
}
},

ReachedManaged: async function ()
{
AppStart.Construct();
try {
await AppStart.WaitForReached();
} finally {
AppStart.RemoveFrame();
}
}
};
</script>
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="appstart.js"></script>

<script defer src="dotnet.js"></script>

Expand Down
10 changes: 1 addition & 9 deletions src/mono/sample/wasm/browser-bench/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,16 @@
var Module = {
config: null,
configSrc: "./mono-config.json",
onConfigLoaded: function () {
if (MONO.config.enable_profiler) {
MONO.config.aot_profiler_options = {
write_at: "Sample.Test::StopProfile",
send_to: "System.Runtime.InteropServices.JavaScript.Runtime::DumpAotProfileData"
}
}
},
onDotNetReady: function () {
try {
App.init();
} catch (error) {
console.log("exception: " + error);
test_exit(1);
throw (error);
}
},
onAbort: function (err) {
test_exit(1);

},
};
3 changes: 3 additions & 0 deletions src/mono/sample/wasm/browser-bench/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
iframe {
display:none;
}

0 comments on commit 35704e4

Please sign in to comment.