Skip to content

Commit

Permalink
Async Runner with Message Channel (#458)
Browse files Browse the repository at this point in the history
  • Loading branch information
flashdesignory authored Jan 15, 2025
1 parent d6b5ffe commit d979723
Show file tree
Hide file tree
Showing 18 changed files with 147 additions and 48 deletions.
3 changes: 2 additions & 1 deletion resources/benchmark-runner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,8 @@ export class BenchmarkRunner {
async runSuite(suite) {
// FIXME: Encapsulate more state in the SuiteRunner.
// FIXME: Return and use measured values from SuiteRunner.
const suiteRunnerClass = SUITE_RUNNER_LOOKUP[suite.type ?? "default"];
const type = suite.type ?? ((params.useAsyncSteps && "async") || "default");
const suiteRunnerClass = SUITE_RUNNER_LOOKUP[type];
const suiteRunner = new suiteRunnerClass(this._frame, this._page, params, suite, this._client, this._measuredValues);
await suiteRunner.run();
}
Expand Down
9 changes: 8 additions & 1 deletion resources/developer-mode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function createDeveloperModeContainer() {
settings.append(createUIForWarmupSuite());
settings.append(createUIForWarmupBeforeSync());
settings.append(createUIForSyncStepDelay());
settings.append(createUIForAsyncSteps());

content.append(document.createElement("hr"));
content.append(settings);
Expand All @@ -45,6 +46,12 @@ function createUIForWarmupSuite() {
});
}

function createUIForAsyncSteps() {
return createCheckboxUI("Use Async Steps", params.useAsyncSteps, (isChecked) => {
params.useAsyncSteps = isChecked;
});
}

function createCheckboxUI(labelValue, initialValue, paramsUpdateCallback) {
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
Expand Down Expand Up @@ -255,7 +262,7 @@ function updateURL() {
}
}

const defaultParamKeys = ["iterationCount", "useWarmupSuite", "warmupBeforeSync", "waitBeforeSync"];
const defaultParamKeys = ["iterationCount", "useWarmupSuite", "warmupBeforeSync", "waitBeforeSync", "useAsyncSteps"];
for (const paramKey of defaultParamKeys) {
if (params[paramKey] !== defaultParams[paramKey])
url.searchParams.set(paramKey, params[paramKey]);
Expand Down
2 changes: 1 addition & 1 deletion resources/newssite/news-next/dist/404.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-4fe6038a10d156ec.js" defer=""></script><script src="./_next/static/chunks/pages/_error-54de1933a164a1ff.js" defer=""></script><script src="./_next/static/slmX88Sy3MO0SLK8cVHnz/_buildManifest.js" defer=""></script><script src="./_next/static/slmX88Sy3MO0SLK8cVHnz/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"slmX88Sy3MO0SLK8cVHnz","assetPrefix":".","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/pages/_error-54de1933a164a1ff.js" defer=""></script><script src="./_next/static/dKI4-ZnFPRMFEw-AlcuLr/_buildManifest.js" defer=""></script><script src="./_next/static/dKI4-ZnFPRMFEw-AlcuLr/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"dKI4-ZnFPRMFEw-AlcuLr","assetPrefix":".","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>

This file was deleted.

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

This file was deleted.

Large diffs are not rendered by default.

This file was deleted.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

2 changes: 1 addition & 1 deletion resources/newssite/news-next/dist/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><link rel="preload" href="./_next/static/css/69253d842fd3fbfd.css" as="style"/><link rel="stylesheet" href="./_next/static/css/69253d842fd3fbfd.css" data-n-p=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-4fe6038a10d156ec.js" defer=""></script><script src="./_next/static/chunks/553-29fd8c03a09c7b37.js" defer=""></script><script src="./_next/static/chunks/pages/index-7052462c4e106c39.js" defer=""></script><script src="./_next/static/slmX88Sy3MO0SLK8cVHnz/_buildManifest.js" defer=""></script><script src="./_next/static/slmX88Sy3MO0SLK8cVHnz/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"slmX88Sy3MO0SLK8cVHnz","assetPrefix":".","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="./_next/static/css/a0dca1379a01e5cf.css" as="style"/><link rel="stylesheet" href="./_next/static/css/a0dca1379a01e5cf.css" data-n-g=""/><link rel="preload" href="./_next/static/css/2cf5163b53bb0adb.css" as="style"/><link rel="stylesheet" href="./_next/static/css/2cf5163b53bb0adb.css" data-n-p=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="./_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="./_next/static/chunks/webpack-e50e9853db18b759.js" defer=""></script><script src="./_next/static/chunks/framework-2c79e2a64abdb08b.js" defer=""></script><script src="./_next/static/chunks/main-2ba37e62325cc71b.js" defer=""></script><script src="./_next/static/chunks/pages/_app-77983e68be50f72a.js" defer=""></script><script src="./_next/static/chunks/743-fd706aeabb7828e3.js" defer=""></script><script src="./_next/static/chunks/pages/index-5268ea812327eb5e.js" defer=""></script><script src="./_next/static/dKI4-ZnFPRMFEw-AlcuLr/_buildManifest.js" defer=""></script><script src="./_next/static/dKI4-ZnFPRMFEw-AlcuLr/_ssgManifest.js" defer=""></script></head><body><div id="__next"></div><div id="settings-container"></div><div id="notifications-container"></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"dKI4-ZnFPRMFEw-AlcuLr","assetPrefix":".","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
3 changes: 3 additions & 0 deletions resources/shared/params.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export class Params {
tags = [];
// Toggle running a dummy suite once before the normal test suites.
useWarmupSuite = false;
// toggle async type vs default raf type.
useAsyncSteps = false;
// Change how a test measurement is triggered and async time is measured:
// "timer": The classic (as in Speedometer 2.x) way using setTimeout
// "raf": Using rAF callbacks, both for triggering the sync part and for measuring async time.
Expand Down Expand Up @@ -50,6 +52,7 @@ export class Params {
this.tags = this._parseTags(searchParams);
this.developerMode = this._parseBooleanParam(searchParams, "developerMode");
this.useWarmupSuite = this._parseBooleanParam(searchParams, "useWarmupSuite");
this.useAsyncSteps = this._parseBooleanParam(searchParams, "useAsyncSteps");
this.waitBeforeSync = this._parseIntParam(searchParams, "waitBeforeSync", 0);
this.warmupBeforeSync = this._parseIntParam(searchParams, "warmupBeforeSync", 0);
this.measurementMethod = this._parseMeasurementMethod(searchParams);
Expand Down
51 changes: 50 additions & 1 deletion resources/shared/test-invoker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class TestInvoker {
}
}

export class RAFTestInvoker extends TestInvoker {
class BaseRAFTestInvoker extends TestInvoker {
start() {
return new Promise((resolve) => {
if (this._params.waitBeforeSync)
Expand All @@ -16,7 +16,9 @@ export class RAFTestInvoker extends TestInvoker {
this._scheduleCallbacks(resolve);
});
}
}

class RAFTestInvoker extends BaseRAFTestInvoker {
_scheduleCallbacks(resolve) {
requestAnimationFrame(() => this._syncCallback());
requestAnimationFrame(() => {
Expand All @@ -31,7 +33,54 @@ export class RAFTestInvoker extends TestInvoker {
}
}

class AsyncRAFTestInvoker extends BaseRAFTestInvoker {
static mc = new MessageChannel();
_scheduleCallbacks(resolve) {
let gotTimer = false;
let gotMessage = false;
let gotPromise = false;

const tryTriggerAsyncCallback = () => {
if (!gotTimer || !gotMessage || !gotPromise)
return;

this._asyncCallback();
setTimeout(async () => {
await this._reportCallback();
resolve();
}, 0);
};

requestAnimationFrame(async () => {
await this._syncCallback();
gotPromise = true;
tryTriggerAsyncCallback();
});

requestAnimationFrame(() => {
setTimeout(async () => {
await Promise.resolve();
gotTimer = true;
tryTriggerAsyncCallback();
});

AsyncRAFTestInvoker.mc.port1.addEventListener(
"message",
async function () {
await Promise.resolve();
gotMessage = true;
tryTriggerAsyncCallback();
},
{ once: true }
);
AsyncRAFTestInvoker.mc.port1.start();
AsyncRAFTestInvoker.mc.port2.postMessage("speedometer");
});
}
}

export const TEST_INVOKER_LOOKUP = {
__proto__: null,
raf: RAFTestInvoker,
async: AsyncRAFTestInvoker,
};
46 changes: 41 additions & 5 deletions resources/shared/test-runner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,28 @@ export class TestRunner {
#suite;
#test;
#callback;
#type;

constructor(frame, page, params, suite, test, callback) {
constructor(frame, page, params, suite, test, callback, type) {
this.#suite = suite;
this.#test = test;
this.#params = params;
this.#callback = callback;

this.#page = page;
this.#frame = frame;
this.#type = type;
}

get page() {
return this.#page;
}

get test() {
return this.#test;
}

_runSyncStep(test, page) {
test.run(page);
}

async runTest() {
Expand All @@ -30,7 +43,7 @@ export class TestRunner {
let asyncStartTime;
let asyncTime;

const runSync = () => {
const runSync = async () => {
if (this.#params.warmupBeforeSync) {
performance.mark("warmup-start");
const startTime = performance.now();
Expand All @@ -41,7 +54,12 @@ export class TestRunner {
}
performance.mark(syncStartLabel);
const syncStartTime = performance.now();
this.#test.run(this.#page);

if (this.#type === "async")
await this._runSyncStep(this.test, this.page);
else
this._runSyncStep(this.test, this.page);

const mark = performance.mark(syncEndLabel);
const syncEndTime = mark.startTime;

Expand All @@ -68,9 +86,27 @@ export class TestRunner {
};

const report = () => this.#callback(this.#test, syncTime, asyncTime);
const invokerClass = TEST_INVOKER_LOOKUP[this.#params.measurementMethod];
const invokerType = this.#suite.type === "async" || this.#params.useAsyncSteps ? "async" : this.#params.measurementMethod;
const invokerClass = TEST_INVOKER_LOOKUP[invokerType];
const invoker = new invokerClass(runSync, measureAsync, report, this.#params);

return invoker.start();
}
}

export class AsyncTestRunner extends TestRunner {
constructor(frame, page, params, suite, test, callback, type) {
super(frame, page, params, suite, test, callback, type);
}

async _runSyncStep(test, page) {
await test.run(page);
}
}

export const TEST_RUNNER_LOOKUP = {
__proto__: null,
default: TestRunner,
async: AsyncTestRunner,
remote: TestRunner,
};
7 changes: 5 additions & 2 deletions resources/suite-runner.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TestRunner } from "./shared/test-runner.mjs";
import { TEST_RUNNER_LOOKUP } from "./shared/test-runner.mjs";
import { WarmupSuite } from "./benchmark-runner.mjs";

export class SuiteRunner {
Expand Down Expand Up @@ -75,7 +75,9 @@ export class SuiteRunner {
if (this.#client?.willRunTest)
await this.#client.willRunTest(this.#suite, test);

const testRunner = new TestRunner(this.#frame, this.#page, this.#params, this.#suite, test, this._recordTestResults);
const testRunnerType = this.#suite.type ?? this.params.useAsyncSteps ? "async" : "default";
const testRunnerClass = TEST_RUNNER_LOOKUP[testRunnerType];
const testRunner = new testRunnerClass(this.#frame, this.#page, this.#params, this.#suite, test, this._recordTestResults, testRunnerType);
await testRunner.runTest();
}
performance.mark(suiteEndLabel);
Expand Down Expand Up @@ -214,5 +216,6 @@ export class RemoteSuiteRunner extends SuiteRunner {
export const SUITE_RUNNER_LOOKUP = {
__proto__: null,
default: SuiteRunner,
async: SuiteRunner,
remote: RemoteSuiteRunner,
};

0 comments on commit d979723

Please sign in to comment.