From 35704e44e5d1b158f21512b1c1081a0e025bde3f Mon Sep 17 00:00:00 2001 From: Radek Doulik Date: Fri, 12 Nov 2021 15:34:56 +0100 Subject: [PATCH] [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 | --- src/mono/sample/wasm/browser-bench/AppStart.cs | 88 ++++++++++++++++++++++ src/mono/sample/wasm/browser-bench/BenchTask.cs | 27 +++++-- src/mono/sample/wasm/browser-bench/Program.cs | 3 +- .../browser-bench/Wasm.Browser.Bench.Sample.csproj | 4 + .../sample/wasm/browser-bench/appstart-frame.html | 41 ++++++++++ src/mono/sample/wasm/browser-bench/appstart.js | 31 ++++++++ src/mono/sample/wasm/browser-bench/index.html | 22 ++++++ src/mono/sample/wasm/browser-bench/runtime.js | 10 +-- src/mono/sample/wasm/browser-bench/style.css | 3 + 9 files changed, 212 insertions(+), 17 deletions(-) create mode 100644 src/mono/sample/wasm/browser-bench/AppStart.cs create mode 100644 src/mono/sample/wasm/browser-bench/appstart-frame.html create mode 100644 src/mono/sample/wasm/browser-bench/appstart.js create mode 100644 src/mono/sample/wasm/browser-bench/style.css diff --git a/src/mono/sample/wasm/browser-bench/AppStart.cs b/src/mono/sample/wasm/browser-bench/AppStart.cs new file mode 100644 index 0000000..98fe46c --- /dev/null +++ b/src/mono/sample/wasm/browser-bench/AppStart.cs @@ -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)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)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)functionCall.Invoke(jsUIReachedManagedFunction, null); + } + } + } +} diff --git a/src/mono/sample/wasm/browser-bench/BenchTask.cs b/src/mono/sample/wasm/browser-bench/BenchTask.cs index 72c86e8..45f87a7 100644 --- a/src/mono/sample/wasm/browser-bench/BenchTask.cs +++ b/src/mono/sample/wasm/browser-bench/BenchTask.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; -abstract class BenchTask +public abstract class BenchTask { public abstract string Name { get; } readonly List results = new(); @@ -18,7 +18,7 @@ abstract class BenchTask { 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(); @@ -50,14 +50,17 @@ abstract class BenchTask 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 RunBatch(BenchTask task, int milliseconds) { DateTime start = DateTime.Now; DateTime end; @@ -65,12 +68,19 @@ abstract class BenchTask 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; @@ -79,7 +89,10 @@ abstract class BenchTask start = DateTime.Now; for (i = 0; i < steps; i++) { - RunStep(); + if (HasRunStepAsync) + await RunStepAsync(); + else + RunStep(); } end = DateTime.Now; diff --git a/src/mono/sample/wasm/browser-bench/Program.cs b/src/mono/sample/wasm/browser-bench/Program.cs index 441a5b8..0b6dedf 100644 --- a/src/mono/sample/wasm/browser-bench/Program.cs +++ b/src/mono/sample/wasm/browser-bench/Program.cs @@ -15,8 +15,9 @@ namespace Sample { List tasks = new() { + new AppStartTask(), new ExceptionsTask(), - new JsonTask (), + new JsonTask(), new WebSocketTask() }; static Test instance = new Test(); diff --git a/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj b/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj index 665dbf2..68366a9 100644 --- a/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj +++ b/src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj @@ -3,10 +3,14 @@ false runtime.js + true + + + diff --git a/src/mono/sample/wasm/browser-bench/appstart-frame.html b/src/mono/sample/wasm/browser-bench/appstart-frame.html new file mode 100644 index 0000000..4e24c6e --- /dev/null +++ b/src/mono/sample/wasm/browser-bench/appstart-frame.html @@ -0,0 +1,41 @@ + + + + + + App task + + + + + + + + + + + diff --git a/src/mono/sample/wasm/browser-bench/appstart.js b/src/mono/sample/wasm/browser-bench/appstart.js new file mode 100644 index 0000000..5aa11c1 --- /dev/null +++ b/src/mono/sample/wasm/browser-bench/appstart.js @@ -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); + } +}; diff --git a/src/mono/sample/wasm/browser-bench/index.html b/src/mono/sample/wasm/browser-bench/index.html index f70c38c..b163465 100644 --- a/src/mono/sample/wasm/browser-bench/index.html +++ b/src/mono/sample/wasm/browser-bench/index.html @@ -6,6 +6,7 @@ TESTS + @@ -53,10 +54,31 @@ 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(); + } } }; + diff --git a/src/mono/sample/wasm/browser-bench/runtime.js b/src/mono/sample/wasm/browser-bench/runtime.js index bc7f510..f810b52 100644 --- a/src/mono/sample/wasm/browser-bench/runtime.js +++ b/src/mono/sample/wasm/browser-bench/runtime.js @@ -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); - }, }; diff --git a/src/mono/sample/wasm/browser-bench/style.css b/src/mono/sample/wasm/browser-bench/style.css new file mode 100644 index 0000000..d21cd30 --- /dev/null +++ b/src/mono/sample/wasm/browser-bench/style.css @@ -0,0 +1,3 @@ +iframe { + display:none; +} -- 2.7.4