Skip to content

Commit 35704e4

Browse files
authored
[wasm] Add AppStart task to the bench Sample (#61481)
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 |
1 parent 5fa6dd3 commit 35704e4

File tree

9 files changed

+212
-17
lines changed

9 files changed

+212
-17
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Reflection;
7+
using System.Runtime.CompilerServices;
8+
using System.Text.Json;
9+
using System.Threading.Tasks;
10+
11+
namespace Sample
12+
{
13+
public class AppStartTask : BenchTask
14+
{
15+
public override string Name => "AppStart";
16+
public override bool BrowserOnly => true;
17+
18+
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, "System.Runtime.InteropServices.JavaScript.Runtime", "System.Private.Runtime.InteropServices.JavaScript")]
19+
static Type jsRuntimeType = System.Type.GetType("System.Runtime.InteropServices.JavaScript.Runtime, System.Private.Runtime.InteropServices.JavaScript", true);
20+
static Type jsFunctionType = System.Type.GetType("System.Runtime.InteropServices.JavaScript.Function, System.Private.Runtime.InteropServices.JavaScript", true);
21+
[DynamicDependency("InvokeJS(System.String)", "System.Runtime.InteropServices.JavaScript.Runtime", "System.Private.Runtime.InteropServices.JavaScript")]
22+
static MethodInfo invokeJSMethod = jsRuntimeType.GetMethod("InvokeJS", new Type[] { typeof(string) });
23+
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, "System.Runtime.InteropServices.JavaScript.Function", "System.Private.Runtime.InteropServices.JavaScript")]
24+
static ConstructorInfo functionConstructor = jsRuntimeType.GetConstructor(new Type[] { typeof(object[]) });
25+
[DynamicDependency("Call()", "System.Runtime.InteropServices.JavaScript.Function", "System.Private.Runtime.InteropServices.JavaScript")]
26+
static MethodInfo functionCall = jsFunctionType.GetMethod("Call", BindingFlags.Instance | BindingFlags.Public, new Type[] { });
27+
28+
public AppStartTask()
29+
{
30+
measurements = new Measurement[] {
31+
new PageShow(),
32+
new ReachManaged(),
33+
};
34+
}
35+
36+
Measurement[] measurements;
37+
public override Measurement[] Measurements => measurements;
38+
39+
static string InvokeJS(string js)
40+
{
41+
return (string)invokeJSMethod.Invoke(null, new object[] { js });
42+
}
43+
44+
class PageShow : BenchTask.Measurement
45+
{
46+
public override string Name => "Page show";
47+
48+
public override int InitialSamples => 3;
49+
50+
async Task RunAsyncStep()
51+
{
52+
var function = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.StartAppUI();" } });
53+
var task = (Task<object>)functionCall.Invoke(function, new object[] { });
54+
55+
await task;
56+
}
57+
58+
public override bool HasRunStepAsync => true;
59+
60+
public override async Task RunStepAsync()
61+
{
62+
var function = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.PageShow();" } });
63+
await (Task<object>)functionCall.Invoke(function, null);
64+
}
65+
}
66+
67+
class ReachManaged : BenchTask.Measurement
68+
{
69+
public override string Name => "Reach managed";
70+
public override int InitialSamples => 3;
71+
public override bool HasRunStepAsync => true;
72+
73+
static object jsUIReachedManagedFunction = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.ReachedManaged();" } });
74+
static object jsReached = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.reached();" } });
75+
76+
[MethodImpl(MethodImplOptions.NoInlining)]
77+
public static void Reached()
78+
{
79+
functionCall.Invoke(jsReached, null);
80+
}
81+
82+
public override async Task RunStepAsync()
83+
{
84+
await (Task<object>)functionCall.Invoke(jsUIReachedManagedFunction, null);
85+
}
86+
}
87+
}
88+
}

src/mono/sample/wasm/browser-bench/BenchTask.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using System.Text.RegularExpressions;
77
using System.Threading.Tasks;
88

9-
abstract class BenchTask
9+
public abstract class BenchTask
1010
{
1111
public abstract string Name { get; }
1212
readonly List<Result> results = new();
@@ -18,7 +18,7 @@ public async Task<string> RunBatch(List<Result> results, int measurementIdx, int
1818
{
1919
var measurement = Measurements[measurementIdx];
2020
await measurement.BeforeBatch();
21-
var result = measurement.RunBatch(this, milliseconds);
21+
var result = await measurement.RunBatch(this, milliseconds);
2222
results.Add(result);
2323
await measurement.AfterBatch();
2424

@@ -50,27 +50,37 @@ public abstract class Measurement
5050

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

53-
public abstract void RunStep();
53+
public virtual void RunStep() { }
54+
public virtual async Task RunStepAsync() { await Task.CompletedTask; }
55+
56+
public virtual bool HasRunStepAsync => false;
5457

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

60-
public Result RunBatch(BenchTask task, int milliseconds)
63+
public async Task<Result> RunBatch(BenchTask task, int milliseconds)
6164
{
6265
DateTime start = DateTime.Now;
6366
DateTime end;
6467
int i = 0;
6568
try
6669
{
6770
// run one to eliminate possible startup overhead and do GC collection
68-
RunStep();
71+
if (HasRunStepAsync)
72+
await RunStepAsync();
73+
else
74+
RunStep();
75+
6976
GC.Collect();
7077

7178
start = DateTime.Now;
7279
for (i = 0; i < InitialSamples; i++)
73-
RunStep();
80+
if (HasRunStepAsync)
81+
await RunStepAsync();
82+
else
83+
RunStep();
7484
end = DateTime.Now;
7585

7686
var initTs = end - start;
@@ -79,7 +89,10 @@ public Result RunBatch(BenchTask task, int milliseconds)
7989
start = DateTime.Now;
8090
for (i = 0; i < steps; i++)
8191
{
82-
RunStep();
92+
if (HasRunStepAsync)
93+
await RunStepAsync();
94+
else
95+
RunStep();
8396
}
8497
end = DateTime.Now;
8598

src/mono/sample/wasm/browser-bench/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ public partial class Test
1515
{
1616
List<BenchTask> tasks = new()
1717
{
18+
new AppStartTask(),
1819
new ExceptionsTask(),
19-
new JsonTask (),
20+
new JsonTask(),
2021
new WebSocketTask()
2122
};
2223
static Test instance = new Test();

src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
<!-- don't need to run this on helix -->
44
<WasmCopyAppZipToHelixTestDir>false</WasmCopyAppZipToHelixTestDir>
55
<WasmMainJSPath>runtime.js</WasmMainJSPath>
6+
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
67
</PropertyGroup>
78

89
<ItemGroup>
910
<WasmExtraFilesToDeploy Include="index.html" />
11+
<WasmExtraFilesToDeploy Include="appstart-frame.html" />
12+
<WasmExtraFilesToDeploy Include="appstart.js" />
13+
<WasmExtraFilesToDeploy Include="style.css" />
1014
<Compile Remove="Console/Console.cs" />
1115
</ItemGroup>
1216

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE html>
2+
<!-- Licensed to the .NET Foundation under one or more agreements. -->
3+
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
4+
<html>
5+
<head>
6+
<title>App task</title>
7+
<meta charset="UTF-8">
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
9+
</head>
10+
<body>
11+
<h3 id="header">Wasm Browser Sample - App task frame</h3>
12+
<span id="out"></span>
13+
<script type='text/javascript'>
14+
var test_exit = function(exit_code)
15+
{
16+
/* Set result in a tests_done element, to be read by xharness */
17+
var tests_done_elem = document.createElement("label");
18+
tests_done_elem.id = "tests_done";
19+
tests_done_elem.innerHTML = exit_code.toString();
20+
document.body.appendChild(tests_done_elem);
21+
22+
console.log(`WASM EXIT ${exit_code}`);
23+
};
24+
25+
window.addEventListener("pageshow", event => { window.parent.resolveAppStartEvent(event); })
26+
27+
var App = {
28+
init: function () {
29+
INTERNAL.call_static_method("[Wasm.Browser.Bench.Sample] Sample.AppStartTask/ReachManaged:Reached");
30+
},
31+
32+
reached: function() {
33+
window.parent.resolveAppStartEvent("reached");
34+
}
35+
};
36+
37+
</script>
38+
<script type="text/javascript" src="runtime.js"></script>
39+
<script defer src="dotnet.js"></script>
40+
</body>
41+
</html>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
var AppStart = {
2+
Construct: function() {
3+
this._frame = document.createElement('iframe');
4+
document.body.appendChild(this._frame);
5+
},
6+
7+
WaitForPageShow: async function() {
8+
let promise;
9+
let promiseResolve;
10+
this._frame.src = 'appstart-frame.html';
11+
promise = new Promise(resolve => { promiseResolve = resolve; })
12+
window.resolveAppStartEvent = function(event) { promiseResolve(); }
13+
await promise;
14+
},
15+
16+
WaitForReached: async function() {
17+
let promise;
18+
let promiseResolve;
19+
this._frame.src = 'appstart-frame.html';
20+
promise = new Promise(resolve => { promiseResolve = resolve; })
21+
window.resolveAppStartEvent = function(event) {
22+
if (event == "reached")
23+
promiseResolve();
24+
}
25+
await promise;
26+
},
27+
28+
RemoveFrame: function () {
29+
document.body.removeChild(this._frame);
30+
}
31+
};

src/mono/sample/wasm/browser-bench/index.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<title>TESTS</title>
77
<meta charset="UTF-8">
88
<meta name="viewport" content="width=device-width, initial-scale=1.0">
9+
<link rel="stylesheet" href="style.css">
910
</head>
1011
<body onload="onLoad()">
1112
<h3 id="header">Wasm Browser Sample - Simple Benchmark</h3>
@@ -53,10 +54,31 @@ <h3 id="header">Wasm Browser Sample - Simple Benchmark</h3>
5354
if (tasks != '')
5455
INTERNAL.call_static_method("[Wasm.Browser.Bench.Sample] Sample.Test:SetTasks", tasks);
5556
yieldBench ();
57+
},
58+
59+
PageShow: async function ()
60+
{
61+
AppStart.Construct();
62+
try {
63+
await AppStart.WaitForPageShow();
64+
} finally {
65+
AppStart.RemoveFrame();
66+
}
67+
},
68+
69+
ReachedManaged: async function ()
70+
{
71+
AppStart.Construct();
72+
try {
73+
await AppStart.WaitForReached();
74+
} finally {
75+
AppStart.RemoveFrame();
76+
}
5677
}
5778
};
5879
</script>
5980
<script type="text/javascript" src="runtime.js"></script>
81+
<script type="text/javascript" src="appstart.js"></script>
6082

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

src/mono/sample/wasm/browser-bench/runtime.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,16 @@
55
var Module = {
66
config: null,
77
configSrc: "./mono-config.json",
8-
onConfigLoaded: function () {
9-
if (MONO.config.enable_profiler) {
10-
MONO.config.aot_profiler_options = {
11-
write_at: "Sample.Test::StopProfile",
12-
send_to: "System.Runtime.InteropServices.JavaScript.Runtime::DumpAotProfileData"
13-
}
14-
}
15-
},
168
onDotNetReady: function () {
179
try {
1810
App.init();
1911
} catch (error) {
12+
console.log("exception: " + error);
2013
test_exit(1);
2114
throw (error);
2215
}
2316
},
2417
onAbort: function (err) {
2518
test_exit(1);
26-
2719
},
2820
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
iframe {
2+
display:none;
3+
}

0 commit comments

Comments
 (0)